Integrating OpenTelemetry with NestJS and Grafana Tempo

Introduction

Modern distributed applications can get complex very quickly. Tracking down performance bottlenecks or debugging request flows across microservices is challenging without proper observability. That’s where OpenTelemetry (OTel) comes in. Combined with Grafana Tempo and Grafana dashboards, you get a powerful stack to collect, store, and visualize distributed traces.

In this tutorial, we’ll walk through setting up a NestJS application with OTel, running an OTel Collector, Tempo, and Grafana via Docker, and finally seeing our traces in Grafana.


What is OpenTelemetry (OTel)?

OpenTelemetry (OTel) is an open-source observability framework that provides a set of APIs, SDKs, and tools for instrumenting, collecting, and exporting telemetry data (traces, metrics, logs).

Why use OTel?


What is Grafana?

Grafana is a visualization and analytics platform. With the Tempo plugin, you can visualize traces collected by OTel in a powerful, interactive dashboard.


Step 1: Create a NestJS Project

# Install NestJS CLI if not already installed
npm i -g @nestjs/cli

## Create new project
nest new otel-nestjs-demo

cd otel-nestjs-demo

Step 2: Add a Test Module with Controller & Service

nest generate module hello
nest generate controller hello
nest generate service hello
import { Controller, Get } from '@nestjs/common';
import { HelloService } from './hello.service';

@Controller('hello')
export class HelloController {
  constructor(private readonly helloService: HelloService) {}

  @Get()
  getHello(): string {
    return this.helloService.getHello();
  }
}
import { Injectable } from '@nestjs/common';

@Injectable()
export class HelloService {
  getHello(): string {
    return 'Hello from NestJS with OpenTelemetry!';
  }
}

Step 3: Add OpenTelemetry to NestJS

npm install @opentelemetry/api @opentelemetry/sdk-trace-node \
@opentelemetry/auto-instrumentations-node @opentelemetry/resources

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

const traceExporter = new OTLPTraceExporter({
  url: 'http://localhost:4317', // OTel Collector endpoint
});

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'nestjs-otel-demo',
  }),
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start()
  .then(() => console.log('OpenTelemetry initialized'))
  .catch((error) => console.log('Error initializing OTel', error));

process.on('SIGTERM', () => {
  sdk.shutdown().then(() => console.log('Tracing terminated'));
});

import './tracer';  // must be first
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

Step 4: Configure OTel Collector

receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  tempo:
    endpoint: "http://tempo:4317"
    tls:
      insecure: true
  logging:

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [tempo, logging]

Step 5: Run Grafana, Tempo, and OTel Collector with Docker

Run Grafana

docker run -d --name=grafana -p 3001:3000 grafana/grafana

Run Tempo

server:
  http_listen_port: 3200

distributor:
  receivers:
    otlp:
      protocols:
        grpc:
        http:

ingester:
  traces:
    max_block_duration: 5m

storage:
  trace:
    backend: local
    local:
      path: /tmp/tempo

docker run -d --name=tempo -v $(pwd)/tempo.yaml:/etc/tempo.yaml \
  -p 3200:3200 -p 4317:4317 grafana/tempo:latest -config.file=/etc/tempo.yaml

Run OTel Collector

docker run -d --name=otel-collector \
  -v $(pwd)/otel-collector.yaml:/etc/otel-collector.yaml \
  -p 4317:4317 -p 4318:4318 otel/opentelemetry-collector:latest \
  --config=/etc/otel-collector.yaml

Step 6: Test NestJS and View Traces

npm run start:dev

curl http://localhost:3000/hello

Conclusion

We’ve successfully: