Winston Complete Guide | Node.js Logging Library

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:

  1. Use log levels appropriately
  2. Structure your logs with metadata
  3. Configure different transports
  4. Rotate logs to manage disk space
  5. Never log sensitive information

Next Steps:

  • Build with Express
  • Monitor with Prometheus
  • Debug with Tools

Resources: