Introduction
Logging records what an application is doing at runtime, which is essential for diagnosing issues in production. While console.log() is convenient during development, production applications need structured, leveled, and configurable logging provided by dedicated libraries such as Winston or Pino, which support log levels, JSON output, and multiple transports.
Cricket analogy: A commentator's quick shout during a friendly net session is fine, but a Test match needs the official scorer logging every ball, run, and dismissal in a structured scorecard for later review.
Syntax
// Using Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
logger.info('Server started');
logger.error('Something failed', { errorCode: 500 });Explanation
console.log() writes unstructured text to stdout with no severity levels, no timestamps by default, and no way to route output to files or external services. Structured logging libraries like Winston and Pino solve this by supporting log levels (error, warn, info, debug), outputting logs as JSON for easy parsing by log aggregators (like ELK or Datadog), and supporting multiple transports so the same log call can write to the console, a file, and a remote service simultaneously. Pino is optimized for very high throughput with minimal overhead, making it a popular choice for performance-sensitive services.
Cricket analogy: Shouting the score across the field with no record is like console.log; a proper scorebook records overs, wickets, and timestamps in structured columns that Winston or Pino mimic with JSON fields.
Example
// Using Pino with Express
const express = require('express');
const pino = require('pino');
const pinoHttp = require('pino-http');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty', // human-readable output in development
},
});
const app = express();
app.use(pinoHttp({ logger }));
app.get('/api/orders/:id', (req, res) => {
req.log.info({ orderId: req.params.id }, 'Fetching order');
if (req.params.id === '404') {
req.log.warn({ orderId: req.params.id }, 'Order not found');
return res.status(404).json({ error: 'Not found' });
}
res.json({ id: req.params.id, status: 'shipped' });
});
app.listen(3000, () => {
logger.info('Server listening on port 3000');
});Output
By default Pino emits newline-delimited JSON, e.g. {"level":30,"time":1690000000,"msg":"Fetching order","orderId":"404"}. With pino-pretty in development, the same log prints as readable colored text with a timestamp and level label. pino-http automatically logs each incoming request and its response status, and the req.log calls include contextual fields like orderId that are easy to filter and search in a log aggregation tool.
Cricket analogy: Pino's compact JSON line is like the raw ball-by-ball data feed a broadcaster receives, while pino-pretty is the commentator translating that feed into readable, colorful commentary for viewers at home.
Key Takeaways
- console.log() lacks severity levels, structured output, and transport routing, making it unsuitable alone for production.
- Winston and Pino support log levels (error, warn, info, debug) and multiple transports (console, file, remote services).
- Structured JSON logs are easy to parse and search using log aggregation platforms like ELK or Datadog.
- Pino is designed for high throughput with low overhead, while Winston offers rich configurability and formatting options.
Practice what you learned
1. Why is console.log() alone considered insufficient for production logging?
2. Which of the following is a key feature of libraries like Winston and Pino?
3. What is a common reason developers choose Pino over other logging libraries?
4. What format do structured logging libraries typically use to make logs easy to parse by aggregation tools?
Was this page helpful?
You May Also Like
Debugging Node.js Applications
Learn to debug Node.js apps using the built-in inspector, console methods, and Chrome DevTools.
Testing Node Apps with Jest
Learn to write unit and integration tests for Node.js and Express apps using Jest and Supertest.
Environment Variables and Configuration
Learn how to manage configuration and secrets in Node.js apps using environment variables and dotenv.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics