DEV Community

Atlas Whoff
Atlas Whoff

Posted on

OpenTelemetry for Node.js: Distributed Tracing Without the Overhead

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
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)