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?