Observability
The transport ships with two complementary observability surfaces. Both work out of the box, both are designed for production, and both stay invisible when you don't need them.
Distributed tracing → — answers "where did this one request go, and where did it slow down?" Backed by OpenTelemetry spans with W3C Trace Context propagation across services.
Prometheus metrics → — answers "is the system healthy in aggregate? What should I alert on?" Backed by prom-client counters, histograms, and gauges written to a registry your exporter already serves.
Picking what you need
Reach for tracing when debugging a specific request: a slow order, a dropped event, an RPC that failed in a weird way. A single trace shows the full path through client.emit() → consumer → handler → reply across every service that touched the message.
Reach for metrics when running the system day to day: alert if consumer lag exceeds a threshold, dashboard p99 handler latency over time, get paged when the error rate spikes. Aggregated numbers — not individual requests — are what you want here.
Both surfaces complement each other. Most production deployments enable both: traces feed an APM (Sentry, Datadog, Jaeger, Tempo, Honeycomb), metrics feed a Prometheus + Grafana stack, and alerts route through both depending on whether the symptom is "this one request" or "the whole system."
Zero overhead when disabled
Neither surface costs anything when off:
- Tracing: with no OpenTelemetry SDK registered in the host application, every tracer call inside the transport short-circuits to a no-op.
@opentelemetry/apiis an optional peer; nothing in the library forces it into your bundle. - Metrics: with
metricsomitted fromforRoot, the metrics module is never registered.prom-clientis never imported — not even dynamically. Per-message overhead drops to a singleMap.get(~30 nanoseconds) that checks whether anyone is listening.
You can enable either independently, or both. They share the same EventBus for transport events but are otherwise isolated.
Quick start
// 1. Tracing — register any OpenTelemetry SDK before your AppModule loads.
// Sentry, Datadog, NewRelic, or @opentelemetry/sdk-node — pick one.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
new NodeSDK({
serviceName: 'orders-service',
traceExporter: new OTLPTraceExporter({ url: 'http://collector:4318/v1/traces' }),
}).start();
// 2. Metrics — enable in forRoot.
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { JetstreamModule } from '@horizon-republic/nestjs-jetstream';
@Module({
imports: [
PrometheusModule.register(),
JetstreamModule.forRoot({
name: 'orders',
servers: ['nats://localhost:4222'],
metrics: true,
}),
],
})
export class AppModule {}
Continue with Tracing for OpenTelemetry details and Metrics for the full Prometheus catalog.