OpenTelemetry for Node.js: Distributed Tracing Without the Complexity
When a request is slow, where is the time going? Without tracing, you're guessing. With OpenTelemetry, you have a complete map of every operation — database queries, external API calls, queue processing — with timing for each.
What OpenTelemetry Gives You
- Traces: the path a request takes through your system, with timing for each step
- Metrics: aggregate measurements (request rate, error rate, latency percentiles)
- Logs: structured logs correlated with trace IDs
The key benefit: vendor-neutral. Instrument once, send to Jaeger, Datadog, Honeycomb, or any OTLP-compatible backend.
Auto-Instrumentation Setup
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// tracing.ts — must be loaded FIRST, before any other imports
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 { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'api-server',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// package.json
{
"scripts": {
"start": "node --require ./tracing.js dist/server.js"
}
}
Auto-instrumentation automatically traces: HTTP requests, database queries (pg, mysql2, prisma), Redis, gRPC, and more.
Custom Spans
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('api-server');
async function processOrder(orderId: string) {
return tracer.startActiveSpan('processOrder', async (span) => {
span.setAttribute('order.id', orderId);
try {
const order = await db.orders.findUnique({ where: { id: orderId } });
span.setAttribute('order.total', order.total);
await chargeCard(order);
await fulfillOrder(order);
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });
throw err;
} finally {
span.end();
}
});
}
Connecting Traces to Logs
import { context, trace } from '@opentelemetry/api';
function log(message: string, data?: object) {
const span = trace.getActiveSpan();
const traceId = span?.spanContext().traceId;
console.log(JSON.stringify({
message,
traceId, // Link log to trace
...data,
timestamp: new Date().toISOString(),
}));
}
Free Backends
- Jaeger (self-hosted, free): great for development
- Grafana Tempo (self-hosted or cloud): integrates with Grafana dashboards
- Honeycomb (cloud, generous free tier): best query experience
Observability, structured logging, and monitoring patterns — including OTel setup — are part of the production infrastructure in the AI SaaS Starter Kit.
Top comments (0)