How to Log Events and Errors in Node.js

Systematic logging is essential for maintaining, debugging, and monitoring Node.js applications in production environments. This article provides a straightforward guide on how to implement structured logging for application events and errors using two of the most popular logging libraries in the Node.js ecosystem: Winston and Bunyan. You will learn how to configure both tools, structure your log outputs in JSON format, capture error stack traces, and direct logs to different destinations.

Why Structured Logging Matters

Traditional console.log statements are insufficient for production because they lack metadata, log levels, and standard formatting. Structured logging outputs logs as machine-readable JSON, making it easy for log management tools (like Elasticsearch, Datadog, or AWS CloudWatch) to parse, search, and filter your application data.


Logging with Winston

Winston is a highly versatile and customizable logging library. It supports multiple storage transports, allowing you to route logs to the console, files, or external services simultaneously.

1. Installation

Install Winston via npm:

npm install winston

2. Configuration and Usage

Here is a complete configuration setup for Winston that handles standard events, formats outputs as JSON, and captures error stack traces.

const winston = require('winston');

// Create the logger instance
const logger = winston.createLogger({
  level: 'info', // Default minimum log level
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }), // Captures stack traces automatically
    winston.format.json() // Outputs logs in structured JSON
  ),
  transports: [
    // Output logs to the console
    new winston.transports.Console(),
    // Save error logs to a separate file
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    // Save all logs to a combined file
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

// Logging standard events
logger.info('Application started successfully', { port: 3000 });

// Logging errors systematically
try {
  throw new Error('Database connection failed');
} catch (error) {
  logger.error('An error occurred during startup', error);
}

Logging with Bunyan

Bunyan is an alternative logging library designed specifically for JSON logging. It is exceptionally fast and enforces JSON formatting by default, ensuring all logs are structured.

1. Installation

Install Bunyan via npm:

npm install bunyan

2. Configuration and Usage

Bunyan uses “serializers” to format complex JavaScript objects, such as standard Error objects, into JSON properties.

const bunyan = require('bunyan');

// Create the logger instance
const logger = bunyan.createLogger({
  name: 'my-app',
  serializers: {
    err: bunyan.stdSerializers.err // Standard serializer to extract error details
  },
  streams: [
    {
      level: 'info',
      stream: process.stdout // Log info and above to the console
    },
    {
      level: 'error',
      path: './logs/bunyan-error.log' // Log errors to a file
    }
  ]
});

// Logging standard events
logger.info({ event: 'user_login', userId: '12345' }, 'User logged in successfully');

// Logging errors systematically
try {
  throw new Error('Payment gateway timeout');
} catch (error) {
  // Pass the error object using the 'err' key to trigger the serializer
  logger.error({ err: error }, 'Failed to process transaction');
}

Key Best Practices for Systematic Logging