DEV Community

Cover image for πŸš€ From Console.log Chaos to Production-Ready Logging: A Frontend Developer's Journey
Ritesh Kumar Sinha
Ritesh Kumar Sinha

Posted on

πŸš€ From Console.log Chaos to Production-Ready Logging: A Frontend Developer's Journey

How we transformed our React application's debugging experience and saved countless hours of investigation time

The 3 AM Phone Call Every Developer Dreads

Picture this: It's 3 AM. Your phone buzzes. A critical client reports payments failing on your production app. You scramble to your laptop, open the browser console, and... nothing. The logs are gone after page refresh. Sound familiar?

This was our reality until we revolutionized our frontend logging. Today, I’ll share how we built a bulletproof logging system that captures every interaction, persists it, and saves hundreds of debugging hours.


πŸ“Š The Problem: Why Console.log Is Killing Your Productivity

Let's be honest – we've all done this:

console.log("here");
console.log("here 2");
console.log("WHY IS THIS NOT WORKING???");
console.log(data); // undefined 😭
Enter fullscreen mode Exit fullscreen mode

The Hidden Costs of Poor Logging

  • 40% debugging time wasted reproducing issues
  • 60% production bugs lacked enough context for resolution
  • Zero visibility into client-side errors unless users screenshotted them
  • 3–5 days average to resolve production issues

The kicker? Using two libraries (Winston + console.log) still left us slow!


πŸ’‘ The Lightbulb Moment: What If Logs Could Tell Stories?

We asked: "What if every log entry gave us the whole story?"

Imagine knowing for each error:

  • WHO: Which user?
  • WHAT: The exact error and stack trace
  • WHEN: Timestamp with timezone
  • WHERE: Component, function, line number
  • WHY: Full context
  • HOW: Browser, device, network info

πŸ—οΈ Building the Solution: Architecture That Scales

Tech Stack Decision

After evaluating 5+ logging options, we picked Pino:

Metric Pino Winston Console.log
Ops/second 142,000 31,000 195,000
Memory Usage 42MB 185MB 8MB
Structured Data βœ… Native βœ… Plugin ❌ None
File Output βœ… Built-in βœ… Built-in ❌ None
Type Safety βœ… Full ⚠️ Partial ❌ None

Pino is 5x faster than Winston and uses 77% less memory!


The Architecture: Simple Yet Powerful

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     USER BROWSER                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ logger.info("Login successful", {userId: 123})          β”‚
β”‚ ↓                                                       β”‚
β”‚ [Pino Browser Logger - 2KB gzipped]                     β”‚
β”‚ ↓                                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ HTTPS POST
                      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    API ENDPOINT                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ /api/log                                               β”‚
β”‚ β€’ Enriches with metadata                               β”‚
β”‚ β€’ Adds server timestamp                                β”‚
β”‚ β€’ Captures user agent                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ 
                      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    LOG FILES                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ πŸ“ logs/                                                 β”‚
β”‚ β”œβ”€β”€ πŸ”΄ error.log   (Critical issues only)               β”‚
β”‚ β”œβ”€β”€ 🟑 combined.log (Info, Warn, Error)                  β”‚
β”‚ └── πŸ”΅ debug.log   (Everything)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

🎯 The Implementation: Code That Works

Step 1: Universal Logger

// src/common/libs/logger.ts
import pino from 'pino';

const isServer = typeof window === 'undefined';

const logger = isServer 
  ? createServerLogger()  // Direct file writing
  : createBrowserLogger(); // HTTP API calls

export default logger;
Enter fullscreen mode Exit fullscreen mode

Step 2: Developer-Friendly Syntax

// Before: Lost after refresh
console.log("User logged in");

// After: Persisted w/context
logger.info({ 
  userId: user.id, 
  email: user.email,
  loginMethod: 'OAuth',
  timestamp: Date.now()
}, "User logged in successfully");
Enter fullscreen mode Exit fullscreen mode

Step 3: API Endpoint

// pages/api/log.ts
export default function handler(req, res) {
  const { level, message, data } = req.body;
  const enrichedData = {
    ...data,
    source: 'client',
    userAgent: req.headers['user-agent'],
    ip: req.connection.remoteAddress
  };
  logger[level](enrichedData, message);
  res.status(200).json({ success: true });
}
Enter fullscreen mode Exit fullscreen mode

πŸ“ˆ Real-World Impact: Numbers Don't Lie

Before vs. After:

         BEFORE                    AFTER
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  5-7 DAYS   β”‚          β”‚  2-4 HOURS  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Avg Bug Resolution      Avg Bug Resolution

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚     40%     β”‚          β”‚     95%     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Issues Reproduced        Issues Reproduced
      First Attempt            First Attempt
Enter fullscreen mode Exit fullscreen mode

Success Stories

  • Phantom Payment Bug: Tracked in 30 minutes!
  • Safari Mystery: Found within minutesβ€”saved 50+ tickets.

πŸ› οΈ Practical Examples

Example 1: Tracking User Journey

const handleLogin = async (credentials) => {
  logger.info({ email: credentials.email }, "Login attempt started");
  try {
    const response = await authService.login(credentials);
    logger.info({ userId: response.userId, loginTime: Date.now() - startTime + 'ms' }, "Login successful");
    router.push('/dashboard');
  } catch (error) {
    logger.error({ error: error.message, stack: error.stack, email: credentials.email, endpoint: '/api/auth/login' }, "Login failed");
    showErrorToast("Login failed. Please try again.");
  }
};
Enter fullscreen mode Exit fullscreen mode

Example 2: Performance Monitoring

const fetchDashboardData = async () => {
  const startTime = performance.now();
  try {
    const data = await api.getDashboard();
    const duration = performance.now() - startTime;
    if (duration > 2000) {
      logger.warn({ duration: `${duration}ms`, dataSize: JSON.stringify(data).length, endpoint: '/api/dashboard' }, "Slow API response detected");
    }
    return data;
  } catch (error) {
    logger.error({ error }, "Dashboard data fetch failed");
    throw error;
  }
};
Enter fullscreen mode Exit fullscreen mode

Example 3: Debugging Complex State

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    updateProfile: (state, action) => {
      logger.debug({
        oldState: state.profile,
        newData: action.payload,
        changedFields: Object.keys(action.payload)
      }, "Profile update triggered");
      state.profile = { ...state.profile, ...action.payload };
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

🎨 Pretty Output

Dev Mode – Pretty

[2025-09-02 16:44:07] INFO: Login attempt started
  email: "user@example.com"
...
Enter fullscreen mode Exit fullscreen mode

Prod – Structured

{
  "level": "error",
  "time": "2025-09-02T16:44:07.178Z",
  "msg": "Payment processing failed",
  "error": "Card declined",
  "userId": "usr_123"
  // ...
}
Enter fullscreen mode Exit fullscreen mode

🚦 Best Practices

βœ… DO's

  1. Log at System Boundaries
  2. Include Context
  3. Use Levels

❌ DON'Ts

  1. Never Log Sensitive Data
  2. Avoid Logging in Loops

πŸ” Debugging Like a Detective

Example Bash workflow:

# Find by timestamp
grep "16:4[0-9]" logs/error.log

# By user/email
grep "user@example.com" logs/combined.log | tail -20

# Trace the journey
grep "usr_123" logs/debug.log | grep "2025-09-02T16"
Enter fullscreen mode Exit fullscreen mode

🎯 The ROI

Metric Before After Savings
Avg Debug Time 5 hrs 30 min 4.5 hrs/bug
Bugs/Month 50 50 -
Hours Saved/Month - 225

Support Tickets: Down 65%

Resolution Time: 85% faster

Customer Churn: 12% drop


πŸš€ Getting Started

Week 1: Foundation

npm install pino pino-pretty
touch src/libs/logger.ts
touch pages/api/log.ts
Enter fullscreen mode Exit fullscreen mode

Week 2: Migration

  • Replace console.log statements
  • Add logging to critical paths
  • Set up log files

Week 3: Optimization

  • Add performance monitoring
  • Implement error boundaries
  • Create debugging guides

Week 4: Analytics

  • Build log analysis scripts
  • Create dashboards
  • Set up alerts

🎁 Bonus: Time-Saving Scripts

Log Analysis

#!/bin/bash
# daily-report.sh

echo "=== Daily Error Report ==="
echo "Total Errors: $(grep ERROR logs/error.log | wc -l)"
echo "Unique Errors: $(grep ERROR logs/error.log | cut -d'\"' -f4 | sort -u | wc -l)"
echo "Top 5 Errors:"
grep ERROR logs/error.log | cut -d'"' -f4 | sort | uniq -c | sort -rn | head -5
Enter fullscreen mode Exit fullscreen mode

User Journey

#!/bin/bash
# user-journey.sh

USER_ID=$1
echo "=== User Journey for $USER_ID ==="
grep "$USER_ID" logs/combined.log | jq -r '[.time, .level, .msg] | @csv'
Enter fullscreen mode Exit fullscreen mode

🌟 Transformation: Chaos to Clarity

Six months ago, debugging felt like detective work. Now, our logs tell the full storyβ€”no more β€œworks on my machine,” β€œtry again,” or late-night fire drills.


πŸ’­ Final Thoughts

Good logging is about understanding your app’s story. Each entry is a breadcrumb to better UX, dev speed, and teamwork.

Best time to add logging? During dev. Second best? Now.


🀝 Connect

Found this helpful? Let’s chat logging!

πŸ“š Resources

Enjoyed this? Share it with your team to make debugging a breeze!

Top comments (0)