SigNoz
Docs
PricingCustomers
Get Started - Free
Docs
IntroductionContributingMigrate from DatadogSigNoz API
OpenTelemetry
What is OpenTelemetryOpenTelemetry Collector GuideOpenTelemetry Demo
Community
Support
Slack
X
Launch Week
Changelog
Dashboard Templates
DevOps Wordle
Newsletter
KubeCon, Atlanta 2025
More
SigNoz vs DatadogSigNoz vs New RelicSigNoz vs GrafanaSigNoz vs Dynatrace
Careers
AboutTermsPrivacySecurity & Compliance
SigNoz Logo
SigNoz
All systems operational
HIPAASOC-2
SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

Send logs from Node.js Winston logger to SigNoz using OpenTelemetry

Most steps are identical. To adapt this guide, update the endpoint and remove the ingestion key header as shown in Cloud → Self-Hosted.

Prerequisites

  • Node.js version 18 or higher
  • An instance of SigNoz (either Cloud or Self-Hosted)
  • A Node.js application that uses the Winston logger

Send logs to SigNoz

Auto-instrumentation automatically captures:

  • Winston logs with trace correlation
  • HTTP requests (incoming and outgoing)

Step 1: Install dependencies

Install the required OpenTelemetry dependencies:

npm install --save @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/winston-transport
yarn add @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/winston-transport

Step 2: Run the application

A VM is a virtual computer that runs on physical hardware. This includes:

  • Cloud VMs: AWS EC2, Google Compute Engine, Azure VMs, DigitalOcean Droplets
  • On-premise VMs: VMware, VirtualBox, Hyper-V, KVM
  • Bare metal servers: Physical servers running Linux/Unix directly

Use this section if you're deploying your application directly on a server or VM without containerization.

Set the environment variables and run your application using the NODE_OPTIONS environment variable:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
export OTEL_SERVICE_NAME="<service_name>"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node index.js # Replace with your entry file

Verify these values:

  • <service_name>: Your service name.
  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.

If using a local OTel Collector (VM), use:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
export OTEL_SERVICE_NAME="<service_name>"
# Do not set OTEL_EXPORTER_OTLP_HEADERS when using the local collector
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node index.js # Replace with your entry file

Add these environment variables to your deployment manifest:

env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: 'https://ingest.<region>.signoz.cloud:443'
  - name: OTEL_EXPORTER_OTLP_HEADERS
    value: 'signoz-ingestion-key=<your-ingestion-key>'
  - name: OTEL_NODE_RESOURCE_DETECTORS
    value: 'env,host,os'
  - name: OTEL_SERVICE_NAME
    value: '<service_name>'
  - name: NODE_OPTIONS
    value: '--require @opentelemetry/auto-instrumentations-node/register'

Replace the placeholders:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.
  • <service_name>: Your service name.

Avoiding Duplicate Logs

If using the k8s-infra chart (which auto-collects container logs), disable log collection for this application to prevent duplicates.

See how to disable log collection →

Create a Dockerfile in your project root:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies including OpenTelemetry auto-instrumentation
RUN npm install
RUN npm install @opentelemetry/api @opentelemetry/auto-instrumentations-node \
  @opentelemetry/winston-transport

# Copy application code
COPY . .

# Set OpenTelemetry environment variables
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
ENV OTEL_SERVICE_NAME="<service_name>"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"

# Expose application port
EXPOSE 3000

# Start the application
CMD ["node", "index.js"]

Build and run the container:

# Build the image
docker build -t nodejs-winston-app .

# Run the container
docker run -p 3000:3000 nodejs-winston-app

Replace the placeholders in the Dockerfile:

  • <service_name>: Your service name.
  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.

Set environment variables in PowerShell:

$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_NODE_RESOURCE_DETECTORS = "env,host,os"
$env:OTEL_SERVICE_NAME = "<service_name>"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:NODE_OPTIONS = "--require @opentelemetry/auto-instrumentations-node/register"
node index.js # Replace with your entry file

Replace the placeholders:

  • <service_name>: Your service name.
  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.

Note

By default, auto-instrumentation collects traces, metrics, and logs. If you want to collect only logs, you can disable traces and metrics by setting the following environment variables:

export OTEL_TRACES_EXPORTER="none"
export OTEL_METRICS_EXPORTER="none"

Step 1: Install dependencies

Install the required OpenTelemetry dependencies:

npm install --save @opentelemetry/sdk-node@^0.208.0 \
  @opentelemetry/exporter-logs-otlp-http@^0.208.0 \
  @opentelemetry/instrumentation-winston@^0.53.0 \
  @opentelemetry/winston-transport@^0.19.0 \
  winston@^3.18.3 \
  express@^5.2.1
yarn add @opentelemetry/sdk-node@^0.208.0 \
  @opentelemetry/exporter-logs-otlp-http@^0.208.0 \
  @opentelemetry/instrumentation-winston@^0.53.0 \
  @opentelemetry/winston-transport@^0.19.0 \
  winston@^3.18.3 \
  express@^5.2.1

Step 2: Set environment variables

A VM is a virtual computer that runs on physical hardware. This includes:

  • Cloud VMs: AWS EC2, Google Compute Engine, Azure VMs, DigitalOcean Droplets
  • On-premise VMs: VMware, VirtualBox, Hyper-V, KVM
  • Bare metal servers: Physical servers running Linux/Unix directly

Use this section if you're deploying your application directly on a server or VM without containerization.

Set the following environment variables to configure the OpenTelemetry exporter:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="nodejs-winston-logger"

Verify these values:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.

If using a local OTel Collector (VM), use:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
# Do not set OTEL_EXPORTER_OTLP_HEADERS

Add these environment variables to your deployment manifest:

env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: 'https://ingest.<region>.signoz.cloud:443'
  - name: OTEL_EXPORTER_OTLP_HEADERS
    value: 'signoz-ingestion-key=<your-ingestion-key>'
  - name: OTEL_SERVICE_NAME
    value: 'nodejs-winston-logger'

Verify these values:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.

Avoiding Duplicate Logs

If using the k8s-infra chart (which auto-collects container logs), disable log collection for this application to prevent duplicates.

See how to disable log collection →

Create a Dockerfile in your project root:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy instrumentation file and application code
COPY logging-instrumentation.js ./
COPY . .

# Set OpenTelemetry environment variables
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV OTEL_SERVICE_NAME="nodejs-winston-logger"

# Expose application port
EXPOSE 3000

# Start the application
CMD ["node", "index.js"]

Build and run the container:

# Build the image
docker build -t nodejs-winston-app .

# Run the container
docker run -p 3000:3000 nodejs-winston-app

Replace the placeholders in the Dockerfile:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Ingestion key for your SigNoz Cloud, see Ingestion Keys.

Set environment variables in PowerShell:

$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_SERVICE_NAME = "nodejs-winston-logger"

Replace the placeholders:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Ingestion key for your SigNoz Cloud, see Ingestion Keys.

Step 3: Configure OpenTelemetry SDK

Create a file named logging-instrumentation.js or logging-instrumentation.ts in your project root. This code reads the environment variables and configures the OpenTelemetry SDK.

logging-instrumentation.js
const { NodeSDK } = require("@opentelemetry/sdk-node");
const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
const {
  WinstonInstrumentation,
} = require("@opentelemetry/instrumentation-winston");
const { resourceFromAttributes } = require("@opentelemetry/resources");
const { ATTR_SERVICE_NAME } = require("@opentelemetry/semantic-conventions");
const { BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");

// Configure the OTLP log exporter
// It automatically reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS
// from environment variables. If not set, defaults to http://localhost:4318
const logExporter = new OTLPLogExporter({});

// Initialize the OpenTelemetry SDK
const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]:
      process.env.OTEL_SERVICE_NAME || "nodejs-winston-logger",
  }),
  logRecordProcessor: new BatchLogRecordProcessor(logExporter),
  instrumentations: [
    new WinstonInstrumentation({
      // configure the instrumentation
    }),
  ],
});

sdk.start();

// Graceful shutdown
process.on("SIGTERM", () => {
  sdk
    .shutdown()
    .then(() => console.log("OpenTelemetry SDK shut down successfully"))
    .catch((error) => console.error("Error shutting down SDK", error))
    .finally(() => process.exit(0));
});

module.exports = sdk;
logging-instrumentation.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";

// Configure the OTLP log exporter
// It automatically reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS
// from environment variables. If not set, defaults to http://localhost:4318
const logExporter = new OTLPLogExporter({});

// Initialize the OpenTelemetry SDK
const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]:
      process.env.OTEL_SERVICE_NAME || "nodejs-winston-logger",
  }),
  logRecordProcessor: new BatchLogRecordProcessor(logExporter),
  instrumentations: [
    new WinstonInstrumentation({
      // configure the instrumentation
    }),
  ],
});

sdk.start();

// Graceful shutdown
process.on("SIGTERM", () => {
  sdk
    .shutdown()
    .then(() => console.log("OpenTelemetry SDK shut down successfully"))
    .catch((error) => console.error("Error shutting down SDK", error))
    .finally(() => process.exit(0));
});

export default sdk;

Check out the Winston instrumentation options here.

Step 4: Initialize OpenTelemetry SDK

Import the instrumentation at the very top of your main application file (e.g., index.js or index.ts).

index.js
require("./logging-instrumentation"); // Must be imported first

---

const winston = require("winston");
const express = require("express");

// Create your Winston logger as usual
const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

// Your application code
const app = express();

app.get("/", (req, res) => {
  logger.info("Request received", { path: req.path, method: req.method });
  res.send("Hello World!");
});

app.get("/error-log", (req, res) => {
  logger.error("Error log endpoint hit", {
    path: req.path,
    method: req.method,
  });
  res.status(500).send("Error log endpoint");
});

app.listen(3000, () => {
  logger.info("Server listening on port 3000");
});
index.ts
import "./logging-instrumentation"; // Must be imported first

---

import winston from "winston";
import express, { Request, Response } from "express";

// Now create your Winston logger as usual
const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

// Your application code
const app: express.Express = express();

app.get("/", (req: Request, res: Response) => {
  logger.info("Request received", { path: req.path, method: req.method });
  res.send("Hello World!");
});

app.get("/error-log", (req: Request, res: Response) => {
  logger.error("Error log endpoint hit", {
    path: req.path,
    method: req.method,
  });
  res.status(500).send("Error log endpoint");
});

app.listen(3000, () => {
  logger.info("Server listening on port 3000");
});

Step 5: Log Correlation (Optional)

Winston logs created within a tracing span are automatically enriched with span and trace IDs, enabling correlation. The added fields are:

  • trace_id
  • span_id
  • trace_flags

You can use tracer.startActiveSpan to correlate logs with traces. Here is an example:

index.js
const api = require('@opentelemetry/api')

const tracer = api.trace.getTracer('example')
tracer.startActiveSpan('process-order', (span) => {
  logger.info('Processing order', { orderId: '12345' })
  // Log record will include: trace_id, span_id, trace_flags

  span.end()
})
index.ts
import * as api from '@opentelemetry/api'

const tracer = api.trace.getTracer('example')
tracer.startActiveSpan('process-order', (span) => {
  logger.info('Processing order', { orderId: '12345' })
  // Log record will include: trace_id, span_id, trace_flags

  span.end()
})

Step 6: Run the Application

Run your application:

npm run dev
# or your application's start command

Validate Logs

Open your SigNoz instance and navigate to the Logs section to view the logs sent from your application.

Node.js Winston Logs
Node.js Winston Logs
Node.js Winston Logs Detailed View
Node.js Winston Logs Detailed View

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.

Why use it?

  • Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
  • Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
  • Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
  • Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.

See Switch from direct export to Collector for step-by-step instructions to convert your setup.

For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.

Troubleshooting

Why do I see "404 Not Found" errors?

This usually happens if the endpoint URL is incorrect.

  • Cloud: https://ingest.<region>.signoz.cloud:443
  • Self-Hosted: http://localhost:4318

Why are my logs not appearing in SigNoz?

  • Check if the application started successfully and is actually generating logs.
  • Verify your <region> and <your-ingestion-key> are correct.

Next Steps

  • Traces Instrumentation guide
  • Log Query/Filtering guides
  • Alerts setup for logs

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack.

If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Last updated: May 4, 2026

Edit on GitHub

Was this page helpful?

Your response helps us improve this page.

Prev
Pino Node.js
Next
Bunyan Node.js
On this page
Prerequisites
Send logs to SigNoz
Step 1: Install dependencies
Step 2: Run the application
Step 1: Install dependencies
Step 2: Set environment variables
Step 3: Configure OpenTelemetry SDK
Step 4: Initialize OpenTelemetry SDK
Step 5: Log Correlation (Optional)
Step 6: Run the Application
Validate Logs
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Why use it?
Troubleshooting
Next Steps
Get Help

Is this page helpful?

Your response helps us improve this page.