DEV Community

Alex Spinov
Alex Spinov

Posted on

Winston Has Free Production Logging for Node.js — Here's the Complete Setup Guide

Winston remains the most flexible Node.js logger. Here is how to set it up properly for production.

Quick Start

bun add winston
Enter fullscreen mode Exit fullscreen mode
import winston from "winston";

const logger = winston.createLogger({
  level: "info",
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: "error.log", level: "error" }),
    new winston.transports.File({ filename: "combined.log" }),
  ],
});

if (process.env.NODE_ENV !== "production") {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  }));
}

logger.info("Application started", { port: 3000 });
logger.error("Database connection failed", { host: "db.example.com", code: "ECONNREFUSED" });
Enter fullscreen mode Exit fullscreen mode

Custom Formats

const customFormat = winston.format.combine(
  winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
  winston.format.errors({ stack: true }),
  winston.format.printf(({ timestamp, level, message, stack, ...meta }) => {
    let log = `${timestamp} [${level}]: ${message}`;
    if (Object.keys(meta).length) log += ` ${JSON.stringify(meta)}`;
    if (stack) log += `\n${stack}`;
    return log;
  })
);
Enter fullscreen mode Exit fullscreen mode

Log Rotation

bun add winston-daily-rotate-file
Enter fullscreen mode Exit fullscreen mode
import DailyRotateFile from "winston-daily-rotate-file";

const transport = new DailyRotateFile({
  filename: "logs/app-%DATE%.log",
  datePattern: "YYYY-MM-DD",
  maxSize: "20m",
  maxFiles: "14d",
  zippedArchive: true,
});

const logger = winston.createLogger({
  transports: [transport],
});
Enter fullscreen mode Exit fullscreen mode

Multiple Transports

const logger = winston.createLogger({
  transports: [
    // Console for dev
    new winston.transports.Console({ level: "debug" }),
    // File for all logs
    new winston.transports.File({ filename: "combined.log" }),
    // Separate file for errors
    new winston.transports.File({ filename: "error.log", level: "error" }),
    // HTTP transport for log aggregation
    new winston.transports.Http({
      host: "logs.example.com",
      port: 443,
      ssl: true,
      level: "warn",
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

Child Loggers (Scoped Context)

const requestLogger = logger.child({
  requestId: req.headers["x-request-id"],
  userId: req.user?.id,
});

requestLogger.info("Processing order");
requestLogger.info("Payment captured", { amount: 99.99 });
// Both logs include requestId and userId automatically
Enter fullscreen mode Exit fullscreen mode

Express Middleware

import expressWinston from "express-winston";

app.use(expressWinston.logger({
  winstonInstance: logger,
  meta: true,
  msg: "HTTP {{req.method}} {{req.url}} {{res.statusCode}} {{res.responseTime}}ms",
  expressFormat: false,
}));

// Error logging
app.use(expressWinston.errorLogger({
  winstonInstance: logger,
}));
Enter fullscreen mode Exit fullscreen mode

Production Best Practices

  1. JSON format in production, pretty format in dev
  2. Rotate logs daily with size limits
  3. Separate error logs for quick debugging
  4. Add request context (requestId, userId)
  5. Never log sensitive data (passwords, tokens, PII)
// Filter sensitive fields
const sensitiveFilter = winston.format((info) => {
  if (info.password) info.password = "***";
  if (info.token) info.token = "***";
  if (info.creditCard) info.creditCard = "***";
  return info;
})();
Enter fullscreen mode Exit fullscreen mode

Need structured logging for data pipelines? Check out my web scraping actors on Apify Store — clean data with full audit trails. For custom solutions, email spinov001@gmail.com.

Top comments (0)