DEV Community

Cover image for Tracing Node.js Microservices with OpenTelemetry
Željko Šević
Željko Šević

Posted on • Originally published at sevic.dev on

Tracing Node.js Microservices with OpenTelemetry

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

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.0
    ports:
      - 16686:16686
      - 4317:4317
      - 4318:4318
Enter fullscreen mode Exit fullscreen mode

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

Import the tracing module before any other application code:

import './tracing.js';
// ...
Enter fullscreen mode Exit fullscreen mode

Hit the app, then open Jaeger UI → SearchService and pick your service name (for example express-starter-dev in the demo).

Need help with your project?

Get personalized advice on your architecture, code, or career in a 45-minute 1-on-1 consultation.

Book a consultation

Top comments (2)

Collapse
 
eminbustun profile image
Emin Üstün

I added these to my project but I couldn't see any incoming requests in Jaguar UI. Can you help me ?

Collapse
 
zsevic profile image
Željko Šević

is the service shown in Search -> Service menu in Jaeger UI?