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.).

LoggingNode.jsJavaScriptTypeScriptMonitoringDebug

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
});