install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/otel-nestjs-integration" ~/.claude/skills/intense-visions-harness-engineering-otel-nestjs-integration-8d96c5 && rm -rf "$T"
manifest:
agents/skills/codex/otel-nestjs-integration/SKILL.mdsource content
OpenTelemetry NestJS Integration
Integrate OpenTelemetry with NestJS using interceptors, decorators, and module-based configuration
When to Use
- Adding distributed tracing to a NestJS application
- Tracing controller methods, service calls, and guards automatically
- Creating a tracing interceptor that wraps all NestJS handlers
- Integrating with NestJS dependency injection for tracer access
Instructions
- Load OpenTelemetry instrumentation before NestJS bootstraps — create
and load it viainstrumentation.ts
.--require - The
auto-instrumentation creates spans for controllers and handlers automatically.@opentelemetry/instrumentation-nestjs-core - Create a custom
to add business-specific attributes to handler spans.TracingInterceptor - Inject the
via a custom provider for manual instrumentation in services.Tracer - Use
for incoming/outgoing HTTP spans — it works with NestJS's underlying Express/Fastify.@opentelemetry/instrumentation-http
// instrumentation.ts — loaded with --require before NestJS import { NodeSDK } from '@opentelemetry/sdk-node'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { Resource } from '@opentelemetry/resources'; import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; const sdk = new NodeSDK({ resource: new Resource({ [ATTR_SERVICE_NAME]: 'nest-api' }), traceExporter: new OTLPTraceExporter(), instrumentations: [getNodeAutoInstrumentations(), new NestInstrumentation()], }); sdk.start();
// tracing/tracing.module.ts — provide tracer via DI import { Module, Global } from '@nestjs/common'; import { trace, Tracer } from '@opentelemetry/api'; const TRACER_TOKEN = 'OTEL_TRACER'; @Global() @Module({ providers: [ { provide: TRACER_TOKEN, useFactory: (): Tracer => trace.getTracer('nest-api', '1.0.0'), }, ], exports: [TRACER_TOKEN], }) export class TracingModule {} export { TRACER_TOKEN };
// tracing/tracing.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Inject } from '@nestjs/common'; import { Observable, tap } from 'rxjs'; import { Tracer, SpanStatusCode, trace } from '@opentelemetry/api'; import { TRACER_TOKEN } from './tracing.module'; @Injectable() export class TracingInterceptor implements NestInterceptor { constructor(@Inject(TRACER_TOKEN) private readonly tracer: Tracer) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const handler = context.getHandler().name; const controller = context.getClass().name; const span = trace.getActiveSpan(); if (span) { span.setAttribute('nestjs.controller', controller); span.setAttribute('nestjs.handler', handler); span.setAttribute('http.route', request.route?.path ?? request.url); if (request.user?.id) { span.setAttribute('user.id', request.user.id); } } return next.handle().pipe( tap({ error: (error) => { if (span) { span.recordException(error); span.setStatus({ code: SpanStatusCode.ERROR, message: error.message }); } }, }) ); } }
// app.module.ts import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { TracingModule } from './tracing/tracing.module'; import { TracingInterceptor } from './tracing/tracing.interceptor'; @Module({ imports: [TracingModule], providers: [{ provide: APP_INTERCEPTOR, useClass: TracingInterceptor }], }) export class AppModule {}
Details
What NestInstrumentation provides: It creates spans for NestJS-specific operations: pipe execution, guard execution, interceptor execution, and handler execution. This gives visibility into the NestJS middleware pipeline.
Manual tracing in services:
@Injectable() export class OrderService { constructor(@Inject(TRACER_TOKEN) private readonly tracer: Tracer) {} async createOrder(dto: CreateOrderDto): Promise<Order> { return this.tracer.startActiveSpan('OrderService.createOrder', async (span) => { try { span.setAttribute('order.items_count', dto.items.length); const order = await this.orderRepo.save(dto); span.setAttribute('order.id', order.id); return order; } finally { span.end(); } }); } }
NestJS + Fastify: The HTTP instrumentation works with both Express and Fastify adapters. No additional configuration needed.
Exception filters: NestJS exception filters run after interceptors. Add trace context to custom exception filters:
@Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const span = trace.getActiveSpan(); span?.recordException(exception); span?.setStatus({ code: SpanStatusCode.ERROR }); // ... handle response } }
Source
https://opentelemetry.io/docs/languages/js/libraries/#nestjs
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.