Tracing Node.js Microservices with OpenTelemetry
Tags: node, opentelemetry, tracing, jaeger
Published: 2023-06-30 20:17:56

---
title: Tracing Node.js Microservices with OpenTelemetry
published: true
tags: ['node', 'opentelemetry', 'tracing', 'jaeger']
canonical_url: https://sevic.dev/notes/opentelemetry-tracing/
---

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 NewRelic, to name a few. This post covers Jaeger's tracing setup for Node.js projects.

Start by setting up the Docker compose via the `docker-compose up` command. Jaeger UI will be available at [http://localhost:16686](http://localhost:16686).

```yml
version: '3.8'

services:
  jaeger:
    image: jaegertracing/all-in-one:1.46
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=:9411
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - 6831:6831/udp
      - 6832:6832/udp
      - 5778:5778
      - 16685:16685
      - 16686:16686
      - 14268:14268
      - 14269:14269
      - 14250:14250
      - 9411:9411
      - 4317:4317
      - 4318:4318
```

The code below shows setting up the tracing via Jaeger. Jaeger doesn't require a separate exporter package since OpenTelemetry supports it natively. Others need to use an exporter package. Filter traces within Jaeger UI by service name or trace ID stored within the logs.

Use resources and semantic resource attributes to set new fields for the trace, like service name or service version. Auto instrumentation identifies frameworks like Express, protocols like HTTP, databases like Postgres, and loggers like Winston used within the project.

Process spans (units of work in distributed systems) in batches to optimize tracing performance. Also, terminate the tracing during the graceful shutdown.

```js
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const traceExporter = new OTLPTraceExporter({
  url: 'http://localhost:4318/v1/traces',
});

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: `<service-name>-${process.env.NODE_ENV}`,
    [SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version ?? '0.0.0',
    env: process.env.NODE_ENV || '',
  }),
  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 tracing config as the first thing inside the entry file.

```js
import './tracing';
// ...
```

Search &rarr; Service menu should show the service name in Jaeger UI. Happy tracing!

### Demo

The demo with the mentioned examples is available [here](https://sevic.dev/demos).