Logs
tracing::info!, debug!, warn!, error! are the framework’s logging API.
Every event flows through the installed subscriber to the console and, when
OTLP is configured, through the
OpenTelemetryTracingBridge
to OTel logs. Backend collectors index them alongside the traces of the same
request.
Default level per layer
Section titled “Default level per layer”| Concern | Target | Default level |
|---|---|---|
| HTTP request log | nest_rs::access | info |
| HTTP span | nest_rs::http | info |
| Route / endpoint mounting (boot) | nest_rs::routes | info |
| ORM queries | nest_rs::orm | trace |
| Layer composition (per-route guard/pipe/… chains) | nest_rs::layers | trace for the per-route effective-chain dump; debug for the redundant multi-scope lint (HTTP deduped once, WS per gateway); warn for actionable posture smells (reversed authn/authz order, denials, routes with no guard and no #[public] when no global pool is active) |
| Authn / Authz | nest_rs::authn / nest_rs::authz | info (denials warn) |
| WebSocket events | nest_rs::ws | info |
| Queue / Schedule | nest_rs::queue / nest_rs::schedule | info |
| Module lifecycle | nest_rs::module | info |
| App-specific spans | <app>::<feature> | info |
Controllers, resolvers, gateways log at info on success. Services at debug.
Repo at trace. Access denials and security events at warn+ so they
appear under the default RUST_LOG=info. A production deploy should respect
info — no per-request debug! on the hot path.
Structured fields
Section titled “Structured fields”tracing::info!( target: "api::users", user_id = %id, actor = %auth.sub, "creating user",);Always prefer field = %value to format!("{}", value). The OTLP bridge
serializes structured fields directly; a formatted string becomes opaque text
the backend cannot filter on.
The request span (installed by the HTTP interceptor — see
Traces) already carries
request_id, actor_id, tenant_id when an authn module is mounted, so
your structured fields layer on top of those.
Text vs JSON
Section titled “Text vs JSON”The console layer ships both formats. Default is text (pretty for dev). For
prod, set:
NESTRS_OPENTELEMETRY__LOG_FORMAT=jsonJSON drops the current_span and span_list fields — the OTel export carries
span structure, and duplicating it in the console line is noise.
Source location (file:line)
Section titled “Source location (file:line)”To trace a console line back to the exact source that emitted it, enable source location:
NESTRS_OPENTELEMETRY__LOG_SOURCE_LOCATION=trueEach event then carries the emitting file:line:
DEBUG nest_rs::layers: crates/nest-rs-core/src/layer_chain.rs: layer declared at multiple scopes…Off by default — it widens every line and leaks source paths, so it stays out
of production. Scaffolded projects turn it on in .env.development (next to
LOG_LEVEL=debug), so a fresh nestrs new app ships with it on while
developing and off everywhere else. Pin it explicitly with
OpenTelemetryConfig::with_log_source_location(true).
Access log
Section titled “Access log”The HTTP interceptor emits one access event per request on nest_rs::access,
at end-of-body, with byte-accurate bytes and duration_ms:
INFO nest_rs::access: GET /users 200 28ms request_id=01J… bytes=412 client_ip=10.0.0.1 trace_id=4bf92f…The same fields land on the OTel HTTP span (see
Traces), so the access line and
the span line up in the backend. Toggle the access line with
NESTRS_HTTP__ACCESS_LOG=false; the OTel span still opens, so propagation
and OTLP export keep working.
OTel logs bridge
Section titled “OTel logs bridge”The
OpenTelemetryTracingBridge
is wired in only when an OTLP endpoint is configured. Without an endpoint, the
bridge is skipped — it pays a small per-event cost just to drop the event,
which is not worth paying when console-only logging is the goal. When the
endpoint is set, every tracing event becomes an OTel log record with the
same target, level, fields and trace_id.