Winston Complete Guide | Node.js Logging Library
이 글의 핵심
Winston is a versatile logging library for Node.js. It supports multiple transports, custom formats, and is used by millions of applications for production logging.
Introduction
Winston is a simple and universal logging library for Node.js. It’s designed to be flexible and extensible, supporting multiple transports (outputs) and formats.
Why Winston?
console.log (basic):
console.log('User logged in:', userId);
console.error('Database connection failed:', error);
Problems:
- ❌ No log levels
- ❌ Can’t filter logs
- ❌ No file output
- ❌ No formatting control
- ❌ Not production-ready
Winston:
logger.info('User logged in', { userId });
logger.error('Database connection failed', { error });
Benefits:
- ✅ Multiple log levels
- ✅ Multiple outputs (console, file, database)
- ✅ Structured logging
- ✅ Custom formats
- ✅ Production-ready
1. Installation
npm install winston
2. Basic Setup
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
],
});
logger.info('Application started');
logger.warn('Low memory warning');
logger.error('Database connection failed');
3. Log Levels
Winston uses npm log levels by default:
{
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
const logger = winston.createLogger({
level: 'info', // Only log 'info' and above (warn, error)
});
logger.error('Critical error'); // Logged
logger.warn('Warning message'); // Logged
logger.info('Info message'); // Logged
logger.debug('Debug details'); // NOT logged (below 'info')
4. Transports
Console Transport
new winston.transports.Console({
level: 'debug',
format: winston.format.simple(),
});
File Transport
new winston.transports.File({
filename: 'error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
});
new winston.transports.File({
filename: 'combined.log',
});
Multiple Transports
const logger = winston.createLogger({
transports: [
// Console for development
new winston.transports.Console({
level: 'debug',
format: winston.format.simple(),
}),
// Error file
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
// Combined file
new winston.transports.File({
filename: 'logs/combined.log',
}),
],
});
5. Formats
Built-in Formats
const { format } = winston;
// JSON format
format.json();
// Simple format
format.simple();
// Pretty print
format.prettyPrint();
// Timestamp
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' });
// Colorize (for console)
format.colorize();
// Align
format.align();
Combining Formats
const logger = winston.createLogger({
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.errors({ stack: true }),
format.splat(),
format.json()
),
});
Custom Format
const customFormat = format.printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} [${level}]: ${message} ${
Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''
}`;
});
const logger = winston.createLogger({
format: format.combine(
format.timestamp(),
customFormat
),
transports: [new winston.transports.Console()],
});
6. Structured Logging
// Bad: string interpolation
logger.info(`User ${userId} logged in from ${ip}`);
// Good: structured data
logger.info('User logged in', {
userId,
ip,
userAgent: req.headers['user-agent'],
timestamp: new Date(),
});
// Even better: consistent format
logger.info('User login', {
event: 'user.login',
userId,
metadata: {
ip,
userAgent: req.headers['user-agent'],
},
});
7. Error Logging
const logger = winston.createLogger({
format: format.combine(
format.timestamp(),
format.errors({ stack: true }), // Include stack traces
format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
// Log error with stack trace
try {
throw new Error('Something went wrong');
} catch (error) {
logger.error('Operation failed', { error });
}
// Output includes full stack trace
8. Child Loggers
const logger = winston.createLogger({
format: format.json(),
transports: [new winston.transports.Console()],
});
// Create child logger with default metadata
const userLogger = logger.child({ service: 'user-service' });
const authLogger = logger.child({ service: 'auth-service' });
userLogger.info('User created', { userId: 123 });
// Output: { level: 'info', message: 'User created', service: 'user-service', userId: 123 }
authLogger.warn('Failed login attempt', { ip: '192.168.1.1' });
// Output: { level: 'warn', message: 'Failed login attempt', service: 'auth-service', ip: '192.168.1.1' }
9. Production Setup
const winston = require('winston');
const { format } = winston;
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.errors({ stack: true }),
format.splat(),
format.json()
),
defaultMeta: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
transports: [
// Error logs
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
// All logs
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880,
maxFiles: 5,
}),
],
exceptionHandlers: [
new winston.transports.File({ filename: 'logs/exceptions.log' }),
],
rejectionHandlers: [
new winston.transports.File({ filename: 'logs/rejections.log' }),
],
});
// Development: also log to console
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: format.combine(
format.colorize(),
format.simple()
),
}));
}
module.exports = logger;
10. Express Integration
const express = require('express');
const winston = require('winston');
const expressWinston = require('express-winston');
const app = express();
// Log all requests
app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'logs/requests.log' }),
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
meta: true, // Log request/response metadata
msg: 'HTTP {{req.method}} {{req.url}}',
expressFormat: true,
colorize: false,
}));
// Your routes
app.get('/', (req, res) => {
res.send('Hello World');
});
// Log errors
app.use(expressWinston.errorLogger({
transports: [
new winston.transports.File({ filename: 'logs/error.log' }),
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
}));
app.listen(3000);
11. Log Rotation
npm install winston-daily-rotate-file
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const logger = winston.createLogger({
transports: [
new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d', // Keep logs for 14 days
}),
],
});
12. Real-World Example
// logger.js
const winston = require('winston');
const { format } = winston;
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
const level = () => {
const env = process.env.NODE_ENV || 'development';
return env === 'development' ? 'debug' : 'warn';
};
const colors = {
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
};
winston.addColors(colors);
const consoleFormat = format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
format.colorize({ all: true }),
format.printf(
(info) => `${info.timestamp} ${info.level}: ${info.message}`,
),
);
const fileFormat = format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
format.errors({ stack: true }),
format.splat(),
format.json(),
);
const transports = [
new winston.transports.Console({ format: consoleFormat }),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
format: fileFormat,
}),
new winston.transports.File({
filename: 'logs/all.log',
format: fileFormat,
}),
];
const logger = winston.createLogger({
level: level(),
levels,
transports,
});
module.exports = logger;
// Usage
const logger = require('./logger');
logger.debug('Debug message');
logger.http('HTTP request');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message', { error: new Error('Something broke') });
13. Best Practices
1. Use Structured Logging
// Good
logger.info('User action', {
action: 'purchase',
userId: 123,
amount: 99.99,
currency: 'USD',
});
// Bad
logger.info(`User 123 purchased item for $99.99`);
2. Include Context
logger.info('Database query', {
query: 'SELECT * FROM users',
duration: 45,
rowCount: 100,
timestamp: new Date(),
});
3. Don’t Log Sensitive Data
// Bad
logger.info('User login', { password: 'secret123' });
// Good
logger.info('User login', {
userId: 123,
timestamp: new Date(),
});
Summary
Winston provides production-ready logging:
- Multiple transports - console, files, external services
- Log levels for filtering
- Structured logging with metadata
- Custom formats for any output
- Log rotation for file management
Key Takeaways:
- Use log levels appropriately
- Structure your logs with metadata
- Configure different transports
- Rotate logs to manage disk space
- Never log sensitive information
Next Steps:
- Build with Express
- Monitor with Prometheus
- Debug with Tools
Resources: