OpenTelemetry for Node.js: Distributed Tracing Without the Overhead
When a request takes 2 seconds and spans 5 microservices, you need to know exactly which service is slow. OpenTelemetry gives you distributed tracing without vendor lock-in.
Install
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
npm install @opentelemetry/exporter-trace-otlp-http
Setup (run before everything else)
// tracing.ts — import this first in your app entry point
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'api-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
Custom Spans
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-service');
async function processOrder(orderId: string) {
const span = tracer.startSpan('process-order');
span.setAttribute('order.id', orderId);
try {
const result = await doWork(orderId);
span.setAttribute('order.status', 'completed');
return result;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
}
Context Propagation Across Services
import { propagation, context } from '@opentelemetry/api';
// Outgoing HTTP call — inject trace context into headers
async function callDownstream(url: string) {
const headers: Record<string, string> = {};
propagation.inject(context.active(), headers);
return fetch(url, { headers });
}
// Incoming request — extract trace context from headers
app.use((req, res, next) => {
const ctx = propagation.extract(context.active(), req.headers);
context.with(ctx, next);
});
What Gets Auto-Instrumented
With getNodeAutoInstrumentations(), these are traced automatically:
- HTTP/HTTPS requests
- Express routes
- Database queries (pg, mysql, mongodb)
- Redis operations
- gRPC calls
- DNS lookups
Exporter Options
| Backend | Exporter |
|---|---|
| Jaeger | @opentelemetry/exporter-jaeger |
| Zipkin | @opentelemetry/exporter-zipkin |
| Grafana Tempo | OTLP HTTP |
| Honeycomb | OTLP HTTP |
| Datadog | OTLP HTTP |
Local Development with Jaeger
docker run -d --name jaeger \
-p 16686:16686 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
# Set in .env
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
Observability is built into the AI SaaS Starter Kit — OpenTelemetry setup, structured logging, and error tracking pre-configured. $99 one-time at whoffagents.com.
Top comments (0)