DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Integrating Node.js Applications With MCP Servers
  • Simplifying Multi-Cloud Observability With Open Source
  • A Guide to Serverless Node.js Functions Using Google Cloud
  • Front-End Troubleshooting Using OpenTelemetry

Trending

  • Retesting Best Practices for Agile Teams: A Quick Guide to Bug Fix Verification
  • Java Backend Development in the Era of Kubernetes and Docker
  • Java in a Container: Efficient Development and Deployment With Docker
  • Navigating the Complexities of AI-Driven Integration in Multi-Cloud Environments: A Veteran’s Insights
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Monitoring and Observability
  4. Deep Observability in Node.js Using OpenTelemetry and Pino

Deep Observability in Node.js Using OpenTelemetry and Pino

Enhance total observability within a distributed microservice architecture using modern tools such as OpenTelemetry (OTel) and Pino logger.

By 
Lakshmi Narayana Rasalay user avatar
Lakshmi Narayana Rasalay
·
Aug. 04, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
4.4K Views

Join the DZone community and get the full member experience.

Join For Free

As applications become increasingly distributed, debugging performance issues or locating failures in a Node.js backend can be challenging. Logging by itself usually provides limited context to comprehend how a request navigates through many layers of your system. Similarly, you cannot correlate trace data with application-specific events if you use tracing without structured logging. 

That is where OpenTelemetry (OTel) for tracing and Pino for structured logging come in. By combining the two, you get deep observability — blending logs and traces together for an unobstructed view of your system's behavior, thereby speeding up debugging, monitoring, and root cause analysis.

With this article, you will know how to:

  • Configure OpenTelemetry for tracing in Node.js
  • Implement Pino for efficient structured logging
  • Inject trace and span context into logs
  • Link the traces and logs in observability tools like Jaeger or New Relic, or Datadog

What Is OpenTelemetry and Pino Logger?

OpenTelemetry

OpenTelemetry is an open standard for collecting traces, metrics, and logs. In Node.js applications, it helps in stitching together HTTP/HTTPS request traces, Database, and external API spans.

Pino

It is a low-overhead, high-performance Node.js logging library. Unlike console logging, Pino logs asynchronous structured JSONs

Setup:

Shell
 
mkdir otel-pino-express-api && cd otel-pino-express-api
npm init -y
 npm install pino pino-http pino-opentelemetry-transport express \
@opentelemetry/sdk-node @opentelemetry/api \
@opentelemetry/semantic-conventions \
@opentelemetry/resources @opentelemetry/exporter-trace-otlp-http


JavaScript
 
Code Structure:
otel-pino-express-api
├── app.js
├── server.js
├── otel.js
├── logger.js
├── package.json


Package.Json

JSON
 
{
  "name": "otel-pino-express-api",
  "version": "1.0.0",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node --require './otel.js' server.js"
  },
  "dependencies": {
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/sdk-node": "^0.50.0",
    "@opentelemetry/auto-instrumentations-node": "^0.50.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
    "express": "^4.18.2",
    "pino": "^8.15.0",
    "pino-http": "^8.2.0"
  }
}


Logger.js:

JavaScript
 
// logger.js
const pino = require('pino');

const transport = pino.transport({
  target: 'pino-opentelemetry-transport',
  options: {
    serviceName: 'pino-otel-demo',
    logLevel: 'info',
  },
});

const logger = pino(transport);
module.exports = logger;


export default logger;


Otel.js

JavaScript
 
Otel.js:
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces', // OTLP_EXPORTER_OTLP_LOGS_ENDPOINT
headers: {}, 
}),
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'pino-otel-demo',
  }),
});

sdk.start();


App.js

JavaScript
 
require('./otel');

const express = require('express');
const pinoHttp = require('pino-http');
const logger = require('./logger');
const app = express();

app.use(pinoHttp({ logger }));

app.get('/', (req, res) => {
  req.log.info('Handled GET /');
  res.send('Hello from Pino + OpenTelemetry!');
});
export default app;


Server.js

JavaScript
 
import app from './app.js';
import logger from './logger.js';

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  logger.info(`Server listening on port ${PORT}`);
});


Run the API:

Shell
 
npm run start


Key Points

  • pino-opentelemetry-transport: This library acts as a transport for Pino, allowing you to send logs to an OpenTelemetry collector
  • @opentelemetry/sdk-node: This is used to initialize the OpenTelemetry SDK, which manages the log exporter.
  • OTLP_EXPORTER_OTLP_LOGS_ENDPOINT: This environment variable (or OTEL_EXPORTER_OTLP_ENDPOINT) specifies the URL of your OpenTelemetry collector.
  • OTel Collector Configuration: Ensure your collector is configured to accept logs via the OTLP protocol and has appropriate processors (e.g., batch, filter) and exporters (e.g., file, logging).
  • Correlation: OpenTelemetry logs are designed to be correlated with traces and metrics, allowing you to see the full picture of your application's behavior.
  • To export Otel traces to a dashboard, you'll typically forward them to a backend like Grafana, NewRelic, or AWS Xray. For simplicity, let's leverage Jaeger + OTEL SDK.

Run Jaeger Locally

Shell
 
docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 16686:16686 \
  -p 4318:4318 \
  jaegertracing/all-in-one:latest


  • Jaeger UI will be at: http://localhost:16686
  • OTLP HTTP endpoint: http://localhost:4318/v1/traces

Logs, traces, and metrics are all important observability pillars that together provide us with the entire picture of distributed systems. Positioning them strategically, such as positioning counters and logs at entry and exit points and utilizing traces at points of decision, allows us to debug effectively. Correlating signals enables us to easily navigate metrics, investigate request flows, and solve complex problems in distributed systems.

Incident management and observability are also related domains in a close manner. By combining both, you can create a better and more effective system for incident response. Given the powerful tracing that OTEL provides and essential application logging via Pino, it gets easier to track any performance bottlenecks in the deeply nested calls of APIs and their DB calls.

The Pitfalls to Watch Out For

1. Broken Context Propagation

Pitfall: Traces are broken when trace context is not properly propagated from service to service.

How to avoid it: Ensure headers like traceparent are propagated from one service call to another and use OTEL's context propagation APIs or auto-instrumentation where available.

2. Over-Instrumentation and Telemetry Noise

Pitfall: Generating too many spans or logs will overwhelm your system and make it harder to derive meaning.

How to avoid it: Instrument only the crucial parts selectively and use sampling in order to keep the data volume in check.

3. Lack of Correlation Among Traces, Metrics, and Logs

Pitfall: Irrelevant telemetry types get in the way of determining root causes.

How to avoid it: Inject trace IDs into logs and supply consistent resource attributes for all telemetry signals.

4. Resource Conflicts / Multiple SDKs

Pitfall: Tracing fails silently or causes side effects.

How to avoid it:

  • Only initialize one OpenTelemetry SDK per service.
  • Avoid mixing legacy and OTEL SDKs unless designed to interop.
  • Reuse singleton tracer instances.

5. Missing or Inconsistent Service Names

Pitfall: Can't search, group, or trace requests across services.

How to avoid it:

  • Set a consistent service name in each app via Resource configuration.
  • Avoid default values like "unknown_service". If you forget to specify a service name, your traces will show up under a generic name like unknown_service.
  • Always configure a meaningful service.name to help group and identify services in your observability dashboard.

Conclusion

By merging Pino with OpenTelemetry, we have high-performance, structured logging and distributed tracing. It enables deep observability, giving you the opportunity to take a closer examination of how the system behaves and allowing you to fix what needs to be fixed before the final stages of development.

Node.js Observability Software development kit

Opinions expressed by DZone contributors are their own.

Related

  • Integrating Node.js Applications With MCP Servers
  • Simplifying Multi-Cloud Observability With Open Source
  • A Guide to Serverless Node.js Functions Using Google Cloud
  • Front-End Troubleshooting Using OpenTelemetry

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook