Skip to main content
Practice Problems

How to implement logging in Express.js (Morgan, Winston)?

Logging in Express.js

Proper logging is essential for debugging, monitoring, and auditing production applications. Express uses two main logging libraries: Morgan for HTTP request logging and Winston for application-level logging.


Morgan — HTTP Request Logger

Morgan logs every incoming HTTP request:

bash
npm install morgan
js
const morgan = require('morgan'); // Predefined formats app.use(morgan('dev')); // Colored output for development app.use(morgan('combined')); // Apache-style for production app.use(morgan('tiny')); // Minimal output

Format examples:

// dev format: GET /api/users 200 12.345 ms - 250 // combined format: ::1 - - [01/Mar/2026:10:00:00 +0000] "GET /api/users HTTP/1.1" 200 250 // tiny format: GET /api/users 200 250 - 12.345 ms

Custom format:

js
morgan.token('body', (req) => JSON.stringify(req.body)); app.use(morgan(':method :url :status :response-time ms - :body'));

Log to file:

js
const fs = require('fs'); const path = require('path'); const accessLogStream = fs.createWriteStream( path.join(__dirname, 'access.log'), { flags: 'a' } ); app.use(morgan('combined', { stream: accessLogStream }));

Winston — Application Logger

Winston is a versatile logging library with multiple transports:

bash
npm install winston
js
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'my-api' }, transports: [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }) ] }); // Add 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() ) })); }

Usage:

js
logger.info('Server started', { port: 3000 }); logger.warn('Slow query detected', { query: 'SELECT...', duration: 5000 }); logger.error('Database connection failed', { error: err.message });

Combining Morgan + Winston

js
const morganStream = { write: (message) => logger.http(message.trim()) }; app.use(morgan('combined', { stream: morganStream }));

Log Levels

LevelPriorityUse Case
error0Application errors
warn1Deprecations, slow queries
info2Key events (startup, shutdown)
http3HTTP requests
debug4Detailed debugging
verbose5Even more detail
silly6Everything

Best Practices

PracticeDescription
Structured loggingUse JSON format for machine parsing
Request IDAdd unique ID to trace requests across logs
Don't log secretsFilter passwords, tokens, credit cards
Log rotationUse winston-daily-rotate-file
Centralized loggingShip to ELK Stack, Datadog, or CloudWatch
Different levels per envdebug in dev, info in prod
js
// Request ID middleware const { v4: uuid } = require('uuid'); app.use((req, res, next) => { req.requestId = req.headers['x-request-id'] || uuid(); res.setHeader('x-request-id', req.requestId); next(); });

Production tip: Always use structured JSON logging. Attach request IDs for tracing. Ship logs to a centralized service. Never log sensitive data like passwords, tokens, or personal information.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems