Node.js Logging
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: nodejs for comprehensive documentation.
Library Comparison
Library Performance Features Best For
Pino Fastest (10x+ faster) JSON-native, low overhead High-performance APIs
Winston Good Flexible transports, formatting Enterprise apps, flexibility
Bunyan Good JSON-native, streams Legacy projects
console Basic Built-in, no deps Simple scripts, debugging
Pino (Recommended for Performance)
Installation
npm install pino pino-pretty # pino-pretty for dev
Basic Setup
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty', options: { colorize: true } } : undefined, });
// Usage logger.info('Server started'); logger.info({ userId: 123, action: 'login' }, 'User logged in'); logger.error({ err }, 'Database connection failed');
Child Loggers (Request Context)
import { Request, Response, NextFunction } from 'express';
export function requestLogger(req: Request, res: Response, next: NextFunction) { req.log = logger.child({ requestId: crypto.randomUUID(), method: req.method, path: req.path, });
req.log.info('Request started');
res.on('finish', () => { req.log.info({ statusCode: res.statusCode }, 'Request completed'); });
next(); }
Express Integration
import express from 'express'; import pino from 'pino'; import pinoHttp from 'pino-http';
const app = express(); const logger = pino();
app.use(pinoHttp({ logger }));
app.get('/users/:id', (req, res) => { req.log.info({ userId: req.params.id }, 'Fetching user'); // ... });
NestJS Integration
// logger.module.ts import { Module, Global } from '@nestjs/common'; import { LoggerModule as PinoLoggerModule } from 'nestjs-pino';
@Global() @Module({ imports: [ PinoLoggerModule.forRoot({ pinoHttp: { level: process.env.LOG_LEVEL || 'info', transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined, }, }), ], }) export class LoggerModule {}
// usage in service import { Logger } from 'nestjs-pino';
@Injectable() export class UserService { constructor(private readonly logger: Logger) {}
findUser(id: string) { this.logger.log({ userId: id }, 'Finding user'); } }
Winston
Installation
npm install winston
Basic Setup
import winston from 'winston';
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'my-service' }, transports: [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ], });
// Pretty console in development if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), })); }
export { logger };
Usage
import { logger } from './logger';
// Basic logging logger.info('Server started on port 3000'); logger.warn('Cache miss for key: user:123'); logger.error('Database connection failed', { error: err });
// With metadata logger.info('User action', { userId: user.id, action: 'purchase', amount: 99.99, });
Child Loggers
const orderLogger = logger.child({ module: 'orders' }); orderLogger.info('Order created', { orderId: '12345' }); // Output: { module: 'orders', orderId: '12345', message: 'Order created', ... }
Custom Transports
import winston from 'winston'; import DailyRotateFile from 'winston-daily-rotate-file';
const rotateTransport = new DailyRotateFile({ filename: 'logs/app-%DATE%.log', datePattern: 'YYYY-MM-DD', maxSize: '100m', maxFiles: '30d', zippedArchive: true, });
rotateTransport.on('rotate', (oldFilename, newFilename) => { logger.info('Log file rotated', { oldFilename, newFilename }); });
logger.add(rotateTransport);
Express Middleware
import expressWinston from 'express-winston';
app.use(expressWinston.logger({ winstonInstance: logger, meta: true, msg: 'HTTP {{req.method}} {{req.url}} {{res.statusCode}} {{res.responseTime}}ms', expressFormat: false, colorize: false, }));
app.use(expressWinston.errorLogger({ winstonInstance: logger, }));
Log Levels
Level Priority Usage
fatal
60 App crash imminent
error
50 Error conditions
warn
40 Warning conditions
info
30 Normal operations
debug
20 Debug information
trace
10 Very detailed tracing
// Pino logger.fatal('Uncaught exception'); logger.error('Failed to connect to database'); logger.warn('Deprecated API called'); logger.info('Server started'); logger.debug('Query executed'); logger.trace('Entering function');
// Winston (no fatal, uses error) logger.error('Critical error'); logger.warn('Warning'); logger.info('Info'); logger.verbose('Verbose'); // between info and debug logger.debug('Debug'); logger.silly('Trace-level'); // lowest
Structured Logging Best Practices
DO
// Include context logger.info({ userId, orderId, action: 'checkout' }, 'Order placed');
// Log errors properly logger.error({ err, userId }, 'Payment failed');
// Use child loggers for context const reqLogger = logger.child({ requestId, userId }); reqLogger.info('Processing request');
DON'T
// Don't log sensitive data logger.info({ password, creditCard }, 'User registered'); // BAD!
// Don't use string interpolation for objects
logger.info(User ${JSON.stringify(user)} logged in); // BAD!
// Don't log PII without masking logger.info({ email: user.email }); // Consider masking
Sensitive Data Handling
import pino from 'pino';
const logger = pino({ redact: { paths: ['password', 'creditCard', 'req.headers.authorization'], censor: '[REDACTED]', }, });
// Logs: { password: '[REDACTED]', username: 'john' } logger.info({ password: 'secret123', username: 'john' });
Production Configuration
Environment Variables
LOG_LEVEL=info # Log level LOG_FORMAT=json # json or pretty LOG_FILE=logs/app.log # File output
Docker/Container Logging
// Log to stdout (Docker captures this) const logger = pino({ level: process.env.LOG_LEVEL || 'info', // No file transport - Docker handles log collection });
// Ensure logs are flushed process.on('SIGTERM', () => { logger.info('Received SIGTERM, shutting down'); logger.flush(); process.exit(0); });
Correlation IDs
import { AsyncLocalStorage } from 'async_hooks';
const asyncLocalStorage = new AsyncLocalStorage<{ requestId: string }>();
export function withRequestContext(req: Request, res: Response, next: NextFunction) { const requestId = req.headers['x-request-id'] || crypto.randomUUID(); asyncLocalStorage.run({ requestId }, () => next()); }
export function getRequestId(): string | undefined { return asyncLocalStorage.getStore()?.requestId; }
// In logger const logger = pino({ mixin() { return { requestId: getRequestId() }; }, });
When NOT to Use This Skill
-
Python applications: Use python-logging skill instead
-
Java/Spring Boot: Use slf4j and logback skills instead
-
Browser/frontend logging: Different APIs and requirements
-
Simple CLI tools: console.log may be sufficient
-
Framework-specific details: Use winston or pino skills for deep dives
Anti-Patterns
Anti-Pattern Why It's Bad Solution
Using console.log in production No control, no persistence Use Winston or Pino
Synchronous file logging Blocks event loop Use async transports
Logging entire request/response objects Contains sensitive data, huge size Log only necessary fields
No request correlation IDs Can't trace requests across services Use AsyncLocalStorage or child loggers
String interpolation in logs Always evaluated Use template strings or parameterized logging
Missing error stack traces Loses debugging context Always include error object: logger.error({ err })
Quick Troubleshooting
Issue Cause Solution
Performance degradation Synchronous logging Switch to Pino or async Winston transports
Logs not appearing Wrong log level Check LOG_LEVEL environment variable
Unreadable JSON in console No pretty-printer Add pino-pretty or Winston simple format for dev
Memory leak Log files growing unbounded Configure log rotation (daily-rotate-file)
Missing request context No correlation ID Use child loggers or AsyncLocalStorage
Duplicate logs Multiple loggers/transports Consolidate to single logger instance
Reference
-
Quick Reference: Cheatsheet
-
Pino Documentation
-
Winston Documentation