Auto-instrumenting Node.js Applications with Winston Logger

This document shows you how to send logs from your Node.js application using the Winston logging library to SigNoz. The setup automatically correlates your logs with traces when available, providing unified observability in SigNoz.

Prerequisites

  • Existing Node.js application using Winston for logging

Setup

Step 1: Install Required Packages

Install the OpenTelemetry packages needed for auto-instrumentation:

npm i @opentelemetry/sdk-node \
      @opentelemetry/auto-instrumentations-node \
      @opentelemetry/instrumentation-winston \
      @opentelemetry/exporter-trace-otlp-http \
      @opentelemetry/exporter-logs-otlp-http \
      @opentelemetry/winston-transport

Package Breakdown

  • @opentelemetry/sdk-node - Core OpenTelemetry SDK for Node.js runtime
  • @opentelemetry/auto-instrumentations-node - Automatic instrumentation for popular libraries (HTTP, Express, etc.)
  • @opentelemetry/instrumentation-winston - Specific instrumentation for Winston logging library
  • @opentelemetry/exporter-trace-otlp-http - OTLP HTTP exporter for traces
  • @opentelemetry/exporter-logs-otlp-http - OTLP HTTP exporter for logs
  • @opentelemetry/winston-transport - Enables automatic log forwarding to OpenTelemetry Logs SDK

Step 2: Create OpenTelemetry Configuration

Create an OpenTelemetry configuration file at your project root:

// otel.js
'use strict';

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston');

// traces
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');

// logs
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const logsAPI = require('@opentelemetry/api-logs');

// resource
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

// describe the service
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'winston-app',
});

// logs pipeline via LoggerProvider
const loggerProvider = new LoggerProvider({
  resource,
  processors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
});
logsAPI.logs.setGlobalLoggerProvider(loggerProvider);

// traces via NodeSDK
const spanProcessors = [new BatchSpanProcessor(new OTLPTraceExporter())];

const sdk = new NodeSDK({
  resource,
  spanProcessors,
  instrumentations: [
    getNodeAutoInstrumentations(), // Creates spans for HTTP, Express, and more
    new WinstonInstrumentation({
      disableLogCorrelation: false, // inject trace_id, span_id, trace_flags
      disableLogSending: false,     // forward logs to OpenTelemetry Logs SDK
    }),
  ],
});

sdk.start();

// Optional graceful shutdown
process.on('SIGTERM', () => {
  sdk.shutdown().finally(() => process.exit(0));
});

Configuration Details

  • Batch processors are used for production efficiency, batching spans and logs before export
  • Auto-instrumentations automatically create spans for HTTP requests, Express routes, and other common libraries
  • Winston instrumentation injects trace context into your logs and forwards log records to the OpenTelemetry Logs SDK
  • LoggerProvider must be set up globally for Winston log forwarding to work
  • The SDK must be started before your application imports Winston

Step 3: Create Winston Logger Configuration

Create a Winston logger configuration file that your application will use:

// logger.js
'use strict';

const winston = require('winston');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  // simple format to keep output readable in terminal
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.printf(info => {
      const { timestamp, level, message, ...meta } = info;
      const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
      return `${timestamp} ${level}: ${message}${metaStr}`;
    })
  ),
  transports: [new winston.transports.Console()],
  defaultMeta: { service: 'winston-express-demo' } // adds to every line
});

module.exports = logger;

Logger Configuration Details

  • Level: Controls which log levels are output (debug, info, warn, error)
  • Format: Defines how log messages are formatted for readability
  • Transports: Specifies where logs are sent (console, file, etc.)
  • DefaultMeta: Adds metadata to every log entry
  • OpenTelemetry Integration: The Winston instrumentation automatically injects trace context and forwards logs

Using the Logger in Your Application

Import and use the logger in your application code:

// Import the logger in your application main file (Ex: app.js)
const logger = require('./logger');

// Example usage in your application
app.get('/', (req, res) => {
  logger.info('Handling request to root endpoint');
  res.json({ message: 'Hello World' });
});

app.get('/error', (req, res) => {
  logger.error('Simulated error occurred', { 
    userId: 123, 
    endpoint: '/error' 
  });
  res.status(500).json({ error: 'Something went wrong' });
});

Verification

After starting your application:

  1. Make some requests to your application endpoints
  2. Check SigNoz for incoming logs
  3. Verify correlation - your logs should contain trace IDs and be correlated with spans if available
NodeJS Winston Logs with Span ID and Trace ID for Correlation
NodeJS Winston Logs with Span ID and Trace ID for Correlation

Last updated: August 19, 2025

Edit on GitHub

Was this page helpful?