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:
- Make some requests to your application endpoints
- Check SigNoz for incoming logs
- Verify correlation - your logs should contain trace IDs and be correlated with spans if available
