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 π
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) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π― 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;
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");
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 });
}
π 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
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.");
}
};
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;
}
};
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 };
}
}
});
π¨ Pretty Output
Dev Mode β Pretty
[2025-09-02 16:44:07] INFO: Login attempt started
email: "user@example.com"
...
Prod β Structured
{
"level": "error",
"time": "2025-09-02T16:44:07.178Z",
"msg": "Payment processing failed",
"error": "Card declined",
"userId": "usr_123"
// ...
}
π¦ Best Practices
β DO's
- Log at System Boundaries
- Include Context
- Use Levels
β DON'Ts
- Never Log Sensitive Data
- 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"
π― 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
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
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'
π 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!
- π¦ Twitter: @Riteshsinha14Rs
- πΌ LinkedIn: Ritesh Kumar Sinha
- π§ Email: ritesh@w3saas.com
π Resources
Enjoyed this? Share it with your team to make debugging a breeze!
Top comments (0)