DEV Community

Atlas Whoff
Atlas Whoff

Posted on

OpenTelemetry for Node.js: Distributed Tracing Without the Complexity

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
Enter fullscreen mode Exit fullscreen mode
// 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();
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "scripts": {
    "start": "node --require ./tracing.js dist/server.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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)