Skip to content

20 Logging Winston Pino

DodaTech 4 min read

title: Logging with Winston and Pino in Node.js REST APIs weight: 30 date: 2026-06-28 lastmod: 2026-06-28 description: Implement structured logging in Node.js REST APIs using Winston and Pino with log levels, transports, request logging middleware, and log rotation for production. tags: [api-development, nodejs]


Logging with Winston and Pino in Node.js REST APIs provides structured, level-based logging with multiple transports (console, file, external services), request correlation IDs, and log rotation for production debugging and monitoring.

```mermaid
flowchart TD
  A[Application] --> B[Logger]
  B --> C[Transports]
  C --> D[Console]
  C --> E[File]
  C --> F[External Service]
  B --> G[Log Levels]
  G --> H[error, warn, info, debug]
  B --> I[Structured JSON]
  style A fill:#e1f5fe
  style B fill:#c8e6c9
  style D fill:#fff9c4
  style E fill:#fff9c4

Winston is the most popular logging library with a mature plugin ecosystem. Pino is faster and produces JSON output natively. Both support log levels (error, warn, info, debug, trace), multiple transports, and custom formats.

Think of logging like a airplane's flight recorder. It records everything that happens (logs), at different priority levels (info: routine, warn: check this, error: something broke). When something goes wrong, you review the logs to understand what happened.

Example: Winston Logger Setup

const winston = require('winston');
const path = require('path');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'my-api' },
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    }),
    new winston.transports.File({
      filename: path.join(__dirname, '../logs/error.log'),
      level: 'error',
      maxsize: 10 * 1024 * 1024, // 10MB
      maxFiles: 5
    }),
    new winston.transports.File({
      filename: path.join(__dirname, '../logs/combined.log'),
      maxsize: 10 * 1024 * 1024,
      maxFiles: 10
    })
  ]
});

module.exports = logger;

// Usage
logger.info('Server started', { port: 3000 });
logger.warn('Rate limit approach', { remaining: 5, userId: '123' });
logger.error('Database connection failed', { error: err.message });

Example: Pino Logger Setup

const pino = require('pino');
const path = require('path');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: {
    targets: [
      {
        target: 'pino-pretty',
        options: { colorize: true },
        level: 'info'
      },
      {
        target: 'pino/file',
        options: {
          destination: path.join(__dirname, '../logs/combined.log'),
          mkdir: true
        },
        level: 'info'
      },
      {
        target: 'pino/file',
        options: {
          destination: path.join(__dirname, '../logs/error.log')
        },
        level: 'error'
      }
    ]
  }
});

module.exports = logger;

// Usage
logger.info({ port: 3000 }, 'Server started');
logger.warn({ remaining: 5, userId: '123' }, 'Rate limit approaching');
logger.error({ err }, 'Database connection failed');

Example: Request Logging Middleware

const logger = require('./logger');
const { v4: uuidv4 } = require('uuid');

const requestLogger = (req, res, next) => {
  // Add correlation ID
  req.correlationId = req.headers['x-correlation-id'] || uuidv4();
  res.setHeader('X-Correlation-ID', req.correlationId);

  const start = Date.now();
  const originalEnd = res.end;

  res.end = function(...args) {
    const duration = Date.now() - start;

    const logData = {
      correlationId: req.correlationId,
      method: req.method,
      url: req.originalUrl,
      statusCode: res.statusCode,
      duration,
      contentLength: res.getHeader('content-length'),
      userAgent: req.headers['user-agent'],
      ip: req.ip
    };

    if (res.statusCode >= 500) {
      logger.error(logData, 'Request failed');
    } else if (res.statusCode >= 400) {
      logger.warn(logData, 'Request with client error');
    } else {
      logger.info(logData, 'Request completed');
    }

    originalEnd.apply(res, args);
  };

  next();
};

app.use(requestLogger);

Common Mistakes

  1. Logging sensitive information — Never log passwords, tokens, credit card numbers, or personal data. Mask or omit sensitive fields.
  2. Using console.log in production — console.log has no log levels, no structured output, and no transport configuration. Use a proper logging library.
  3. Logging too much or too little — Logging every variable clutters logs and hurts performance. Log too little and you cannot debug issues. Balance is key.
  4. Not rotating log files — Without rotation, log files grow indefinitely and fill disk space. Configure max size and max files.
  5. Not including correlation IDs — Without correlation IDs, you cannot trace a single request across multiple services or log entries.

Practice Questions

  1. What are the standard log levels and when should you use each?
  2. What is the difference between Winston and Pino?
  3. Why should you use structured (JSON) logging?
  4. What is a correlation ID and why is it important?
  5. Challenge: Build a complete logging system for an Express API. Include Winston setup with console and file transports, request logging middleware with correlation IDs, error logging with stack traces, performance logging, and log rotation. Ensure no sensitive data is logged.

FAQ

Should I use Winston or Pino?

Winston has more features and plugins. Pino is 5-10x faster. Choose Pino for high-throughput APIs and Winston when you need advanced formatting or transports.

What log level should I use in production?

info is typical for production. Set debug level temporarily when troubleshooting. Error level alone misses important warnings.

How do I handle log storage and retention?

Use log rotation on the server (max 10MB per file, keep 10 files). Ship logs to a centralized system (ELK, Datadog, Splunk) for long-term storage and search.

What is structured logging?

Structured logging outputs logs as JSON objects with named fields instead of text strings. This makes logs searchable and parseable by log management systems.

How do I avoid logging sensitive data?

Create a sanitize function that removes known sensitive fields (password, ssn, token) from objects before logging. Apply it in the logging middleware.

Mini Project

Build a logging system for an Express API. Configure Winston with console and file transports, add a request logging middleware with correlation IDs, implement log sanitization to remove sensitive fields, set up log rotation, and add structured error logging with stack traces.

What's Next

Now learn about Swagger documentation in Building REST APIs with Node.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro