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 morganjs
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 outputFormat 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 msCustom 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 winstonjs
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
| Level | Priority | Use Case |
|---|---|---|
error | 0 | Application errors |
warn | 1 | Deprecations, slow queries |
info | 2 | Key events (startup, shutdown) |
http | 3 | HTTP requests |
debug | 4 | Detailed debugging |
verbose | 5 | Even more detail |
silly | 6 | Everything |
Best Practices
| Practice | Description |
|---|---|
| Structured logging | Use JSON format for machine parsing |
| Request ID | Add unique ID to trace requests across logs |
| Don't log secrets | Filter passwords, tokens, credit cards |
| Log rotation | Use winston-daily-rotate-file |
| Centralized logging | Ship to ELK Stack, Datadog, or CloudWatch |
| Different levels per env | debug 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 readyPremium
A concise answer to help you respond confidently on this topic during an interview.