Skip to content

NestJS OpenTelemetry instrumentation

This guide instruments a NestJS service with the OpenTelemetry Node.js SDK. NestJS runs on Express or Fastify under the hood, so the auto-instrumentations capture controller routes and outbound calls with no changes to your handlers. The OTLP exporters send traces, metrics, and logs to Osuite.

  • Node.js 18 or newer.
  • A NestJS project that compiles to dist/.
  • An Osuite ingest token from Settings → API Keys.
  1. Install the OpenTelemetry packages

    Terminal window
    npm install @opentelemetry/sdk-node \
    @opentelemetry/auto-instrumentations-node \
    @opentelemetry/exporter-trace-otlp-proto \
    @opentelemetry/exporter-metrics-otlp-proto \
    @opentelemetry/exporter-logs-otlp-proto
  2. Create the tracing module

    Create src/tracing.ts. It starts the SDK on import and is compiled to dist/tracing.js alongside the rest of your build.

    src/tracing.ts
    import { NodeSDK } from '@opentelemetry/sdk-node';
    import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
    import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
    import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
    import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
    import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
    import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
    const sdk = new NodeSDK({
    traceExporter: new OTLPTraceExporter(),
    metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter(),
    }),
    logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
    instrumentations: [getNodeAutoInstrumentations()],
    });
    sdk.start();
    process.on('SIGTERM', () => {
    sdk.shutdown().finally(() => process.exit(0));
    });
  3. Load it before the application boots

    The SDK must start before @nestjs/core and its HTTP platform are loaded. Add this as the very first line of src/main.ts, above every other import:

    src/main.ts
    import './tracing';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    await app.listen(3000);
    }
    bootstrap();

The exporters and resource are configured entirely through environment variables:

  • OTEL_EXPORTER_OTLP_ENDPOINT — your Osuite ingest endpoint.
  • OTEL_EXPORTER_OTLP_HEADERS — the x-osuite-ingest-token header that authenticates ingestion.
  • OTEL_SERVICE_NAME — the service name that identifies this service in Osuite.
  • OTEL_RESOURCE_ATTRIBUTES — additional resource attributes such as service.environment.

Build the project (npm run build), then run the compiled output. Choose your deployment target:

Terminal window
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.osuite.io:443"
export OTEL_EXPORTER_OTLP_HEADERS="x-osuite-ingest-token=<your-ingest-token>"
export OTEL_SERVICE_NAME="billing-service"
export OTEL_RESOURCE_ATTRIBUTES="service.environment=production"
node dist/main.js

Because main.ts imports ./tracing first, no --require flag is needed. If you prefer not to touch main.ts, drop that import and start with node --require ./dist/tracing.js dist/main.js instead.

What you should see

Send a request to a controller route, then open APM. The service appears within a minute under the service.name you set, emitting request-rate and latency metrics. Opening a trace shows the inbound route span with child spans for downstream providers and outbound calls.

No data in Osuite after a few minutes? Work through these checks.

  • Endpoint — confirm the exporter targets exactly ingest.<region>.osuite.io:443 for your region, over TLS.
  • Token — confirm the ingest header carries a valid <your-ingest-token> and has not been rotated.
  • Pipeline — confirm the signal you expect (traces, logs, or metrics) is wired into an active pipeline with the otlp/osuite exporter attached.
  • Export errors — check the application or Collector logs for OTLP export failures (auth, DNS, TLS, connection refused).
  • Network — confirm the host has outbound access to the ingest endpoint on port 443.
  • Timing — allow up to a minute for the first data to appear before assuming a failure.

Still stuck? Ask the Investigation Agent or contact support.

For Node-specific issues — ESM vs CommonJS loading, the --require flag, or missing spans — see Node.js troubleshooting.