tracing subscriber for structured logging, a request-tracing middleware that emits per-request spans and log lines, an optional Prometheus scrape endpoint, and — behind the otel feature flag — OpenTelemetry export for traces, metrics, and logs that can be layered on without changing the rest of the pipeline.
Installing the tracing subscriber
Callconfigure_tracing once, before listen, so all log output and request spans share the same pipeline. The log level is read from NESTRS_LOG first, then RUST_LOG, then the default_directive on TracingConfig (default "info"):
TracingFormat::Pretty is the default and produces human-readable multi-line output. Switch to TracingFormat::Json in production for log aggregation platforms.
Request tracing middleware
use_request_tracing adds a middleware that:
- Records a completion log line with
method,path,status,duration_ms, andrequest_id(whenuse_request_id()is also enabled). - Creates a
tracingspan namedhttp.server.requestfor each request, with fieldshttp.request.methodandhttp.route.
http.route is set to the concrete request path at this middleware layer. Axum’s route template (e.g. /users/:id) is not available here. For OTLP dashboards, treat the literal path as the closest stable route identifier unless you add a custom layer that sets a template field.Prometheus metrics
enable_metrics registers a Prometheus scrape handler at the path you provide (default /metrics). It tracks:
http_request_duration_seconds— histogram with standard bucketshttp_requests_total{method, status}— counterhttp_requests_in_flight— in-flight gauge
/metrics path is mounted at the server root — it is not affected by set_global_prefix or enable_uri_versioning.
Health and readiness checks
Useenable_health_check for a simple liveness probe that always returns 200:
enable_readiness_check when you want to gate traffic on the health of dependencies. Implement HealthIndicator for each dependency and pass the indicators at startup:
HealthStatus::Down, the readiness endpoint returns 503 with a Terminus-style JSON summary containing status, info, error, and details keys.
Probe decorators (#[liveness], #[readiness], #[startup])
Beyond the always-OK liveness route, nestrs can mirror a real handler as a probe. Decorating a route handler with one of the probe decorators mounts a fixed endpoint under /__nestrs/health/* that calls the handler on each scrape (or once, for startup) and reports up/down from its status code:
set_global_prefix or URI versioning) so orchestrators can probe them without prefixes. Probe results are cached for 5 seconds per endpoint to prevent probe-storm self-DoS; a panicking handler reports down with a generic message (raw errors go to tracing only).
If your own routes already occupy
/__nestrs/health/live or /__nestrs/health/ready, your routes win — the framework skips its mirrored endpoint. The builder-level enable_health_check(“/health”) and enable_readiness_check(“/ready”, […]) paths above are unaffected and remain the simplest options for most apps.livenessProbe → /__nestrs/health/live (or your enable_health_check path), readinessProbe → /__nestrs/health/ready (or your readiness path), startupProbe → /__nestrs/health/startup.
OpenTelemetry (OTLP)
Enable theotel feature to export to any OTLP-compatible collector (Jaeger, Tempo, Honeycomb, SigNoz, etc.). Spans export by default; metrics and logs are per-signal opt-ins on the config:
configure_tracing with configure_tracing_opentelemetry and supply an OpenTelemetryConfig:
endpoint is not set, nestrs falls back to the OTEL_EXPORTER_OTLP_ENDPOINT environment variable, then to http://localhost:4317.
Dual metrics export: Prometheus pull + OTLP push
There is exactly one metrics recording surface in the process — themetrics facade — and nestrs owns it with a fan-out recorder. Every metrics::counter! / gauge! / histogram! call is forwarded to every enabled backend:
enable_metrics("/metrics")registers the Prometheus backend (pull, scraped at the path you choose).OpenTelemetryConfig::metrics()registers the OTLP backend (push, on the collector’s schedule).
http_requests_total, http_request_duration_seconds, http_requests_in_flight) and any instruments you define yourself. Use this to bridge a migration (Prometheus today, OTLP tomorrow) or to serve both a local scrape and a central pipeline.
Units are translated to OpenTelemetry’s UCUM-style conventions when pushed: facade
Unit::Seconds exports as s, Unit::Count as 1. Facade labels become OTel attributes 1:1 (counter!(“rps”, “route” => “/x”) exports with attribute route=“/x”).Custom instruments
Record your own metrics through the same surface — themetrics facade is re-exported at nestrs::metrics, so no extra dependency is needed:
nestrs::metrics::describe_counter!(...) etc. carry their unit and description into both backends.
Logs over OTLP
OpenTelemetryConfig::logs() bridges the tracing facade into the OTLP log pipeline: every tracing::info! / warn! / error! event becomes an OpenTelemetry log record on the same collector as your traces. Events emitted inside an active span are correlated with its trace and span IDs, so a trace in Jaeger and its surrounding log lines in Loki/Tempo line up automatically. The local tracing output (pretty or JSON) is unchanged — OTLP is an additional destination, not a replacement.
Runtime requirements and shutdown
- Install from async context. The OTLP exporters are lazy — a missing collector never fails startup — but their transport is spawned onto the current Tokio reactor at construction. Call
configure_tracing_opentelemetryfrom#[tokio::main](beforelisten), exactly as in the examples above. - Metric export cadence follows the collector convention: every 60s by default, overridable with
OTEL_METRIC_EXPORT_INTERVAL(milliseconds). - Shutdown flushes.
NestApplication::listen*methods flush and stop the OTLP pipelines on graceful shutdown, so the last metrics window and log batch reach the collector. Calling the install functions directly (nestrs::otel::install_otlp_meter/install_otlp_logger) pairs withnestrs::otel::shutdown_meter_provider()/shutdown_logger_provider()for custom lifecycles.
Environment variables reference
Troubleshooting
Local dev vs production
- Local development
- Production