20 Logging Winston Pino
title: Logging with Winston and Pino in Node.js REST APIs weight: 30 date: 2026-06-28 lastmod: 2026-06-28 description: Implement structured logging in Node.js REST APIs using Winston and Pino with log levels, transports, request logging middleware, and log rotation for production. tags: [api-development, nodejs]
Logging with Winston and Pino in Node.js REST APIs provides structured, level-based logging with multiple transports (console, file, external services), request correlation IDs, and log rotation for production debugging and monitoring.
```mermaid
flowchart TD
A[Application] --> B[Logger]
B --> C[Transports]
C --> D[Console]
C --> E[File]
C --> F[External Service]
B --> G[Log Levels]
G --> H[error, warn, info, debug]
B --> I[Structured JSON]
style A fill:#e1f5fe
style B fill:#c8e6c9
style D fill:#fff9c4
style E fill:#fff9c4
Winston is the most popular logging library with a mature plugin ecosystem. Pino is faster and produces JSON output natively. Both support log levels (error, warn, info, debug, trace), multiple transports, and custom formats.
Think of logging like a airplane's flight recorder. It records everything that happens (logs), at different priority levels (info: routine, warn: check this, error: something broke). When something goes wrong, you review the logs to understand what happened.
Example: Winston Logger Setup
const winston = require('winston');
const path = require('path');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'my-api' },
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error',
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log'),
maxsize: 10 * 1024 * 1024,
maxFiles: 10
})
]
});
module.exports = logger;
// Usage
logger.info('Server started', { port: 3000 });
logger.warn('Rate limit approach', { remaining: 5, userId: '123' });
logger.error('Database connection failed', { error: err.message });
Example: Pino Logger Setup
const pino = require('pino');
const path = require('path');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
targets: [
{
target: 'pino-pretty',
options: { colorize: true },
level: 'info'
},
{
target: 'pino/file',
options: {
destination: path.join(__dirname, '../logs/combined.log'),
mkdir: true
},
level: 'info'
},
{
target: 'pino/file',
options: {
destination: path.join(__dirname, '../logs/error.log')
},
level: 'error'
}
]
}
});
module.exports = logger;
// Usage
logger.info({ port: 3000 }, 'Server started');
logger.warn({ remaining: 5, userId: '123' }, 'Rate limit approaching');
logger.error({ err }, 'Database connection failed');
Example: Request Logging Middleware
const logger = require('./logger');
const { v4: uuidv4 } = require('uuid');
const requestLogger = (req, res, next) => {
// Add correlation ID
req.correlationId = req.headers['x-correlation-id'] || uuidv4();
res.setHeader('X-Correlation-ID', req.correlationId);
const start = Date.now();
const originalEnd = res.end;
res.end = function(...args) {
const duration = Date.now() - start;
const logData = {
correlationId: req.correlationId,
method: req.method,
url: req.originalUrl,
statusCode: res.statusCode,
duration,
contentLength: res.getHeader('content-length'),
userAgent: req.headers['user-agent'],
ip: req.ip
};
if (res.statusCode >= 500) {
logger.error(logData, 'Request failed');
} else if (res.statusCode >= 400) {
logger.warn(logData, 'Request with client error');
} else {
logger.info(logData, 'Request completed');
}
originalEnd.apply(res, args);
};
next();
};
app.use(requestLogger);
Common Mistakes
- Logging sensitive information — Never log passwords, tokens, credit card numbers, or personal data. Mask or omit sensitive fields.
- Using console.log in production — console.log has no log levels, no structured output, and no transport configuration. Use a proper logging library.
- Logging too much or too little — Logging every variable clutters logs and hurts performance. Log too little and you cannot debug issues. Balance is key.
- Not rotating log files — Without rotation, log files grow indefinitely and fill disk space. Configure max size and max files.
- Not including correlation IDs — Without correlation IDs, you cannot trace a single request across multiple services or log entries.
Practice Questions
- What are the standard log levels and when should you use each?
- What is the difference between Winston and Pino?
- Why should you use structured (JSON) logging?
- What is a correlation ID and why is it important?
- Challenge: Build a complete logging system for an Express API. Include Winston setup with console and file transports, request logging middleware with correlation IDs, error logging with stack traces, performance logging, and log rotation. Ensure no sensitive data is logged.
FAQ
Mini Project
Build a logging system for an Express API. Configure Winston with console and file transports, add a request logging middleware with correlation IDs, implement log sanitization to remove sensitive fields, set up log rotation, and add structured error logging with stack traces.
What's Next
Now learn about Swagger documentation in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro