In production, you can't attach a debugger, logs are often your only window into what actually happened. This lesson covers structured logging and the tools that make production logs actually useful.
Why console.log Isn't Enough in Production
console.log() works fine for local development, but production systems need more: consistent structure (so logs can be searched and filtered), severity levels (so you can distinguish routine info from urgent errors), and often JSON output (so log aggregation tools like Datadog or CloudWatch can parse fields automatically).
Dedicated logging libraries like pino (extremely fast, JSON-first) and winston (highly configurable, supports multiple "transports") solve these problems without you having to build structured logging by hand.
Each log call outputs structured JSON with a severity level, timestamp, and any extra fields you pass, ready to be searched and filtered by a log aggregation system.
Standard Log Levels
fatal - the application is about to crash
error - something failed and needs attention
warn - unexpected but recoverable situation
info - normal, notable application events
debug - detailed information useful during development
Log levels let you filter output by severity in different environments (verbose in dev, quieter in prod).
error/fatal logs should generally trigger alerts; info/debug logs usually shouldn't.
Structured (JSON) logs are far easier to search and filter than freeform text messages.
Avoid logging sensitive data (passwords, full credit card numbers, tokens) at any log level.
Logging Cheatsheet
Common logging library choices and their strengths.
Library
Strengths
Pino
Extremely fast, JSON-first, low overhead
Winston
Highly configurable, multiple transports/formats
Morgan
HTTP request logging middleware for Express
console
Fine for small scripts and local development
Log aggregators
Datadog, CloudWatch, ELK, consume structured logs
Structured vs Unstructured Logs
An unstructured log message like "User 42 logged in from 1.2.3.4" is readable by a human but hard for tooling to search reliably. A structured log, logger.info({ userId: 42, ip: '1.2.3.4' }, 'User logged in'), keeps the same information as searchable, filterable fields.
Logging Every Request
Middleware like Morgan (or a custom logger) records every incoming request automatically, method, path, status code, and response time, giving you a complete, consistent record of traffic without adding logging calls to every individual route.
import morgan from 'morgan';
app.use(morgan('combined')); // logs every request in a standard format
In production, pair request logging with a structured logger like Pino so both request-level and application-level logs share the same searchable format.
Common Mistakes
Logging sensitive data like passwords, tokens, or full payment details, even temporarily during debugging.
Using only unstructured string messages, making production logs difficult to search or filter reliably.
Not distinguishing log levels, treating every message as equally important (or unimportant).
Relying entirely on console.log() in production instead of a proper logging library with levels and structure.
Key Takeaways
Production logging needs structure and severity levels beyond what plain console.log() provides.
Libraries like Pino and Winston provide fast, structured, JSON-first logging.
Standard log levels (fatal, error, warn, info, debug) let you filter noise appropriately per environment.
Never log sensitive data like passwords or tokens, at any log level.
Pro Tip
Adopt a structured logging library from the start of any project meant for production, even a small one. Retrofitting structured logs onto thousands of scattered console.log() calls later is far more tedious than starting with logger.info({...}, 'message') from day one.
You now log effectively for production diagnostics. Next, learn Node.js performance techniques for keeping your application fast under load.