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:
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Complete Winston guide for Node.js logging. Learn transports, formats, log levels, error handling, and building producti…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- PM2 Complete Guide | Production Process Manager for Node.js
- Nodemailer Complete Guide | Send Emails from Node.js
- Passport.js Complete Guide
Keywords Covered in This Article (Related Search Terms)
This article covers Winston, Logging, Node.js, Backend, Monitoring, DevOps.