Tracing Node.js Microservices with OpenTelemetry
June 30, 2023Regarding microservices observability, tracing is important to catch bottlenecks of the services like slow requests and database queries.
OpenTelemetry is a set of monitoring tools that support integration with distributed tracing platforms like Jaeger, Zipkin, and New Relic. This post covers Jaeger v2 tracing setup for Node.js projects.
Prerequisites
- Docker
- Node.js version 26
- OpenTelemetry packages:
npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \@opentelemetry/exporter-trace-otlp-http @opentelemetry/resources \@opentelemetry/sdk-trace-base @opentelemetry/semantic-conventions express
Jaeger v2
Start Jaeger in all-in-one mode with Docker Compose. Jaeger UI is at http://localhost:16686. OTLP HTTP receiver listens on port 4318.
services:jaeger:image: jaegertracing/jaeger:2.19.0ports:- 16686:16686- 4317:4317- 4318:4318
Run docker compose up -d from the demo folder (see below).
OpenTelemetry setup
Send traces to Jaeger over OTLP HTTP. Use resourceFromAttributes and semantic convention constants (ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION) to label the service. Auto-instrumentation picks up Express, HTTP clients, databases, and other supported libraries.
Process spans in batches with BatchSpanProcessor and shut the SDK down gracefully on SIGTERM.
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';import { resourceFromAttributes } from '@opentelemetry/resources';import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';import { NodeSDK } from '@opentelemetry/sdk-node';import {ATTR_DEPLOYMENT_ENVIRONMENT_NAME,ATTR_SERVICE_NAME,ATTR_SERVICE_VERSION,} from '@opentelemetry/semantic-conventions';const traceExporter = new OTLPTraceExporter({url: 'http://localhost:4318/v1/traces',});const sdk = new NodeSDK({resource: resourceFromAttributes({[ATTR_SERVICE_NAME]: `<service-name>-${process.env.NODE_ENV ?? 'dev'}`,[ATTR_SERVICE_VERSION]: process.env.npm_package_version ?? '0.0.0',[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: process.env.NODE_ENV ?? 'dev',}),instrumentations: [getNodeAutoInstrumentations()],spanProcessor: new BatchSpanProcessor(traceExporter),});sdk.start();process.on('SIGTERM', () => {sdk.shutdown().then(() => console.log('Tracing terminated')).catch((error) => console.error('Error terminating tracing', error)).finally(() => process.exit(0));});
Import the tracing module before any other application code:
import './tracing.js';// ...
Hit the app, then open Jaeger UI → Search → Service and pick your service name (for example express-starter-dev in the demo).
Demo
Runnable code for this post lives in the jaeger-tracing-demo folder. Get access via code demos.