Winston
Most versatile logging library for Node.js (17,000+ GitHub stars). Provides rich transport and format options with extensive customization. Enables automatic tracking of uncaught exceptions and sending logs to multiple cloud logging services (AWS CloudWatch, logz.io, etc.).
Library
Winston
Overview
Winston is known as "A logger for just about everything" and is the most popular logging library in the Node.js ecosystem. With its modular design, it provides multiple transports (output destinations) and flexible formatting capabilities, supporting a wide range of scenarios from development to production environments. It's a proven library used by over 25,350 projects and continues active development.
Details
Winston 3.17.0 remains the de facto standard logger in the Node.js ecosystem with active development as of 2025. Designed to "simplify the creation, formatting, and transport of log messages," it supports simultaneous output to console, files, HTTP endpoints, databases, and more. It addresses modern requirements with Express.js integration via express-winston and OpenTelemetry support for cloud-native applications.
Key Features
- Multi-Transport Support: Simultaneous output to console, files, HTTP, cloud services, and more
- Flexible Formatting: JSON, custom printf, and combinable formatters
- Log Level Management: NPM style levels (error, warn, info, http, verbose, debug, silly)
- Profiling Capabilities: Execution time measurement and performance analysis
- Exception Handling: Automatic logging of unhandled exceptions and Promise rejections
- Express.js Integration: Comprehensive HTTP request/response logging functionality
Pros and Cons
Pros
- Most established logger in Node.js ecosystem (10+ years of development)
- Rich transport options (50+ including third-party transports)
- High extensibility and customization (custom formats, transports)
- Production-ready features (exception handling, profiling, performance optimization)
- Rich ecosystem (express-winston, winston-daily-rotate-file, etc.)
- Comprehensive documentation and abundant community resources
Cons
- Steep learning curve with complex initial configuration
- TypeScript type safety may be inferior to newer libraries
- Performance tuning required for high-volume logging
- Memory usage increase with complex configurations
- Mix of legacy APIs and modern JavaScript syntax
- Error messages during debugging can sometimes be unclear
References
Code Examples
Basic Setup
npm install winston
npm install winston-daily-rotate-file # For rotation functionality
npm install express-winston # For Express.js integration
Simple Logger Creation
const winston = require('winston');
// Basic logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// Add console output in development environment
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
// Log output
logger.error('Error message');
logger.warn('Warning message');
logger.info('Info message');
logger.debug('Debug message');
Using Custom Formats
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf, colorize } = format;
// Custom format definition
const myFormat = printf(({ level, message, label, timestamp, ...meta }) => {
const metaString = Object.keys(meta).length ? JSON.stringify(meta, null, 2) : '';
return `${timestamp} [${label}] ${level}: ${message} ${metaString}`;
});
const logger = createLogger({
level: 'debug',
format: combine(
label({ label: 'MyApp' }),
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
colorize(),
myFormat
),
transports: [
new transports.Console(),
new transports.File({
filename: 'app.log',
format: format.json() // File output in JSON format
})
]
});
// Usage example
logger.info('User logged in', {
userId: 123,
email: '[email protected]',
ip: '192.168.1.1'
});
Multiple Transport Configuration
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
// Daily rotation file configuration
const errorTransport = new DailyRotateFile({
filename: 'logs/error-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
level: 'error',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
});
const combinedTransport = new DailyRotateFile({
filename: 'logs/combined-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d'
});
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
errorTransport,
combinedTransport,
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
// HTTP transport for remote logging
logger.add(new winston.transports.Http({
host: 'log-server.example.com',
port: 8080,
path: '/logs',
level: 'error'
}));
Express.js Integration
const express = require('express');
const expressWinston = require('express-winston');
const winston = require('winston');
const app = express();
// Request logging (before route handlers)
app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'requests.log' })
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
),
meta: true, // Include request/response metadata
msg: "HTTP {{req.method}} {{req.url}}",
expressFormat: true,
colorize: false,
ignoreRoute: function (req, res) {
return false; // Log all routes
}
}));
// Application routes
app.get('/', (req, res) => {
res.send('Hello World!');
});
// Error logging (after route handlers)
app.use(expressWinston.errorLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'errors.log' })
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
)
}));
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Profiling and Exception Handling
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()
),
transports: [
new winston.transports.File({ filename: 'combined.log' })
],
// Exception handling
exceptionHandlers: [
new winston.transports.File({ filename: 'exceptions.log' })
],
// Promise rejection handling
rejectionHandlers: [
new winston.transports.File({ filename: 'rejections.log' })
]
});
// Profiling functionality
logger.profile('database-query');
// Database operation simulation
setTimeout(() => {
logger.profile('database-query', { message: 'Database query completed' });
}, 1000);
// Timer functionality
const profiler = logger.startTimer();
setTimeout(() => {
profiler.done({ message: 'Async operation completed' });
}, 2000);
// Structured logging
logger.info('User action log', {
action: 'login',
userId: 'user123',
timestamp: new Date().toISOString(),
metadata: {
userAgent: 'Mozilla/5.0...',
ip: '192.168.1.100',
sessionId: 'sess_abc123'
}
});
Custom Log Levels and Filtering
const winston = require('winston');
// Custom log level definition
const customLevels = {
levels: {
fatal: 0,
error: 1,
warn: 2,
info: 3,
debug: 4,
trace: 5
},
colors: {
fatal: 'red',
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'blue',
trace: 'gray'
}
};
// Custom filter (exclude sensitive information)
const sensitiveFilter = winston.format((info) => {
if (info.private || info.password || info.secret) {
return false; // Don't log
}
return info;
});
const logger = winston.createLogger({
levels: customLevels.levels,
format: winston.format.combine(
sensitiveFilter(),
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console({
level: 'debug',
format: winston.format.combine(
winston.format.colorize({ colors: customLevels.colors }),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'app.log',
level: 'info'
})
]
});
// Using custom levels
logger.fatal('Fatal error occurred');
logger.trace('Detailed trace information');
// Filtered log (won't be output)
logger.info('Login attempt', {
username: 'user123',
password: 'secret123' // This information won't be logged
});