NestJS Tracing with OpenTelemetry: Production Setup Guide
Set up NestJS tracing with OpenTelemetry and Tracekit so you can follow requests, catch regressions, and inspect runtime state without redeploying.

NestJS tracing with OpenTelemetry should answer three production questions fast: which request failed, where the time went, and what runtime state explains the bad outcome. If your current workflow stops at "the controller was slow" or "the logs did not show the missing variable," you do not have enough production context yet.
This guide shows how to set up NestJS tracing with OpenTelemetry using Tracekit's current Node.js and NestJS support, what the SDK traces automatically today, where you still need manual instrumentation, and how dynamic logs help when a trace shows the hot path but not the reason the code made a bad decision.
The focus is HTTP-based NestJS applications first. If you also run queue workers, background jobs, or Nest microservice transports, keep reading: those flows usually need extra trace context propagation and manual spans.
What good NestJS tracing should show
The official NestJS request lifecycle docs describe a request moving through middleware, guards, interceptors, pipes, and the handler. Your tracing setup should make that path explainable in production instead of leaving you with isolated log lines.
For a practical production setup, you want:
| Area | What to see | Why it matters |
|---|---|---|
| Incoming request | route, method, status, duration, trace ID | Confirms which request actually hurt users |
| NestJS context | controller and handler names | Tells you which code path handled the request |
| Dependencies | outbound HTTP, database, cache, queue edges where instrumented | Reveals where latency or failures happened |
| Error context | exception, stack trace, release context where available | Turns "500 somewhere" into a fixable incident |
| Runtime state | bounded variable capture on suspicious code paths | Explains why the branch decision was wrong |
Tracekit's current NestJS support is grounded in the public Node.js docs and the local SDK codebase:
TracekitModule.forRoot()registers a global interceptor for NestJS HTTP requests.- The interceptor records
nestjs.controller,nestjs.handler, HTTP metadata, status, and duration. - The Node SDK also supports dynamic logs, manual spans, exception capture, and standard OpenTelemetry-style traces for downstream work.
That matters because the live SERP for nestjs tracing is dominated by tutorials, not vendor homepages. Developers searching this keyword usually want a setup guide plus production debugging advice, not just a features page.
1. Install Tracekit's NestJS module
Tracekit's current Node.js docs include first-class NestJS setup through @tracekit/node-apm/nestjs.
npm install @tracekit/node-apm
// app.module.ts
import { Module } from '@nestjs/common';
import { TracekitModule } from '@tracekit/node-apm/nestjs';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TracekitModule.forRoot({
apiKey: process.env.TRACEKIT_API_KEY!,
serviceName: 'api-gateway',
endpoint: 'https://app.tracekit.dev/v1/traces',
enableCodeMonitoring: true,
}),
UsersModule,
],
})
export class AppModule {}
If you prefer config-driven setup, use forRootAsync() and wire the values through ConfigModule.
Two practical notes:
- Keep
serviceNamestable per deployable service. If every environment or pod writes a different service name, your traces get harder to query. - Turn on
enableCodeMonitoringif you want dynamic logs later. The tracing setup is still useful without it, but you lose the fastest path to runtime state when the trace alone is not enough.
For the exact package setup, use Tracekit's Node.js integration guide. If you are starting from scratch with OTLP, the OpenTelemetry config generator is a useful companion.
2. Know what Tracekit traces automatically today
Tracekit's NestJS path is not a vague "it works with Node somehow" claim. The current SDK exports a dedicated NestJS module and a global interceptor. That interceptor starts a trace for HTTP requests and adds request attributes plus nestjs.controller and nestjs.handler.
In practice, this means the default setup is strongest for:
- HTTP request tracing
- controller and handler attribution
- outgoing HTTP spans when the relevant client libraries are instrumented
- database spans when supported libraries are loaded after initialization
- exception capture tied to the request
That is enough to solve a lot of real production problems. You can see which controller handled a request, whether the slow step was your database or an external API, and whether the regression started after a deployment.
It is also important to stay honest about the current boundary:
- The NestJS interceptor only auto-traces HTTP execution contexts.
- Queue consumers, cron jobs, background workers, and other non-HTTP flows need their own instrumentation strategy.
- If your architecture uses Nest microservices, BullMQ, Kafka, or custom event pipelines, treat trace propagation as an explicit checklist item, not something to assume happened automatically.
That distinction is the difference between a useful guide and a misleading one.
3. Keep trace context intact across service boundaries
The W3C Trace Context specification defines the standard headers used to propagate trace identity between services. If those headers disappear at a proxy boundary or never get attached to async work, your waterfall stops telling the whole story.
For NestJS applications, the minimum production checklist is:
- propagate
traceparentand related context through outbound HTTP calls - preserve context through API gateways, reverse proxies, and service meshes
- instrument external clients and database libraries after Tracekit initializes
- add explicit context propagation for queues, workers, and event-driven handoffs
OpenTelemetry's HTTP semantic conventions also matter here. Consistent route, method, and status attributes make traces much easier to search and compare across services.
If you skip this step, you still get spans, but you do not always get one coherent trace. That usually shows up as:
- orphan downstream spans
- missing parent-child relationships
- impossible-to-read cross-service incidents
- "the request looked healthy in service A but service B timed out" with no shared trace ID
Tracekit helps once the context exists. It cannot infer missing propagation after the fact.
4. Add manual spans where NestJS abstractions hide real work
Automatic tracing is the floor, not the ceiling.
The official OpenTelemetry JavaScript instrumentation docs recommend combining automatic instrumentation with manual spans where business logic or framework abstractions matter. That applies directly to NestJS because a lot of important work happens behind decorators, services, guards, validation, and provider calls.
Good candidates for manual spans include:
- a service method that fans out to multiple dependencies
- a custom guard that does remote authorization work
- a provider that performs expensive transformation or enrichment
- queue publish or consume boundaries
- any code path where the request succeeds technically but returns the wrong business result
Tracekit's current Node SDK supports manual spans from NestJS services:
import { Injectable, Inject } from '@nestjs/common';
import { TracekitClient } from '@tracekit/node-apm/nestjs';
@Injectable()
export class BillingService {
constructor(
@Inject('TRACEKIT_CLIENT') private tracekit: TracekitClient
) {}
async calculateInvoice(accountId: string) {
const span = this.tracekit.startSpan('billing.calculate-invoice', null, {
'account.id': accountId,
'operation.type': 'billing',
});
try {
const result = await this.invoiceRepository.calculate(accountId);
this.tracekit.endSpan(span, {
'rows.count': result.items.length,
});
return result;
} catch (error) {
this.tracekit.recordException(span, error as Error);
this.tracekit.endSpan(span, {}, 'ERROR');
throw error;
}
}
}
This is where NestJS tracing becomes more than "one span per request." You can expose the expensive business step directly instead of hoping the root request span is detailed enough.
5. Use dynamic logs when the trace shows where to look but not why
Tracing answers timing and path questions well. It does not always answer state questions.
That is where Tracekit dynamic logs fit. They are not a generic log ingestion system. They are bounded capture points for the exact moment the suspicious code path runs.
Use them when a trace already told you which request or service is suspicious, but you still need to inspect:
- a DTO value after validation
- a feature flag decision
- an account- or tenant-specific branch
- a payload coming back from a third-party API
- the variable that should have been logged but was not
Tracekit's Node SDK exposes this through captureSnapshot(). The public workflow is dynamic logs; the SDK method name still uses the internal snapshot terminology.
import { Injectable, Inject } from '@nestjs/common';
import { TracekitClient } from '@tracekit/node-apm/nestjs';
@Injectable()
export class CheckoutService {
constructor(
@Inject('TRACEKIT_CLIENT') private tracekit: TracekitClient
) {}
async validateOrder(userId: string, amount: number, couponCode?: string) {
await this.tracekit.captureSnapshot('checkout-validation', {
userId,
amount,
couponCode,
});
return this.pricingEngine.validate(userId, amount, couponCode);
}
}
That lets you inspect runtime state without adding another permanent log line and waiting on a redeploy. For the broader workflow, see Tracekit's dynamic logs documentation and the dynamic logs feature page.
6. Build your NestJS tracing dashboard around triage questions
A useful NestJS tracing setup should help the on-call developer answer:
- Which route or handler regressed?
- Did the issue start after a release?
- Was the bottleneck inside NestJS code, the database, or an external service?
- Did trace context survive the full request path?
- If the timings look normal, what runtime state explains the wrong outcome?
That is why Tracekit's product surface matters for this topic. The value is not only the waterfall. It is the combination of:
- distributed tracing
- release-aware debugging and error context
- dynamic logs
- a production-safe path to inspect runtime state
If your current tooling stops at route latency, you still have work to do.
Production checklist for NestJS tracing
Before you call your setup production-ready, verify these items:
- Each deployable NestJS service has a stable
serviceName. - Incoming HTTP requests create traces with route, method, status, and duration.
- Controller and handler names appear where expected.
- Outbound HTTP and database work create child spans where those libraries are instrumented.
traceparentpropagation survives service-to-service calls.- Queue jobs, cron work, and non-HTTP transports have explicit instrumentation plans.
- Exceptions are connected to the request trace.
- Dynamic logs are enabled for services where runtime-state debugging matters.
- Sensitive data is reviewed before adding capture points.
For teams shipping quickly, that last pair matters a lot. You want runtime visibility, but you want it bounded and intentional.
When Tracekit is the right fit for this keyword
Tracekit is a strong fit for nestjs tracing with OpenTelemetry because the product already has:
- a real NestJS module in the Node SDK
- HTTP request tracing with NestJS-specific attributes
- manual span support for deeper service instrumentation
- dynamic logs for runtime-state capture
- OpenTelemetry-native ingestion and trace workflows
Just keep the positioning accurate. Tracekit is not a generic log-ingestion platform, and the current NestJS auto-tracing path is strongest for HTTP applications first. When you need async or transport-specific coverage, add the instrumentation deliberately.
If you want a practical next step, start with the Node.js and NestJS docs, verify one HTTP request end to end, then add manual spans and dynamic logs only where the default trace stops being explanatory.
Related Posts

OTel Trace Viewer Guide: How to Inspect OpenTelemetry Traces Faster
Compare OTel trace viewer options, learn how to read OpenTelemetry traces, and inspect OTLP, Jaeger, or Zipkin payloads faster.
Your API Just Threw a 500. Here's How to Actually Fix It.
Your API threw a 500 at 3 AM. A systematic approach to diagnose, trace, and fix production API errors without the panic and guesswork.
Zero-Cost Production Visibility: APM Without the Datadog Bill
Get production visibility without the Datadog bill. Free-tier APM setup with OpenTelemetry for freelancers and small teams who need traces, not invoices.