OpenTelemetry
nest-rs-opentelemetry is the OpenTelemetry layer of the framework. It installs
a tracing subscriber, batches traces / logs /
metrics through the official
opentelemetry +
opentelemetry_sdk +
opentelemetry-otlp crates,
follows the
opentelemetry-semantic-conventions
for resource and HTTP attributes, and links the tracing span tree to its OTel
counterpart through
tracing-opentelemetry.
Three signal types ship in one crate, behind one import:
- Traces — every request, job and scheduled tick
opens a span; W3C
traceparentpropagation, sampling, and the per-request HTTP interceptor stitch distributed traces together. - Logs —
tracing::info!and friends flow to the console and to OTel logs (viaopentelemetry-appender-tracing) with structured fields, configurable level per layer, and JSON for prod. - Metrics — inject
Arc<OpenTelemetryMeter>and build counters / histograms / gauges; metrics batch on aPeriodicReaderand export through the same OTLP endpoint as the traces.
Install
Section titled “Install”cargo add nest-rs-opentelemetryA headless binary (a queue worker) stops there; an app serving HTTP adds the
http feature for the per-request interceptor — see Feature
flags below.
Wiring it up
Section titled “Wiring it up”use anyhow::Result;use nest_rs_config::Environment;use nest_rs_core::App;use nest_rs_opentelemetry::OpenTelemetry;
use api::ApiModule;
#[tokio::main]async fn main() -> Result<()> { let _environment = Environment::init(); let _opentelemetry = OpenTelemetry::init("api")?;
App::builder() .module::<ApiModule>() .build() .await? .run() .await}#[module( imports = [ OpenTelemetryModule, ServerTimingModule, ],)]pub struct ApiModule;Two pieces, one rule. OpenTelemetry::init(name)? installs the global
subscriber and must run before App::builder(). The returned guard flushes
batch exporters on Drop, so it must live until main returns.
OpenTelemetryModule then mounts the per-request HTTP interceptor and exposes
the meter as a provider.
Configure the exporter
Section titled “Configure the exporter”Every option of OpenTelemetryConfig is settable via env or via the pinned
struct — the framework-wide config dual path.
OpenTelemetry::init(name) reads env; OpenTelemetry::init_with(cfg) takes a
pinned OpenTelemetryConfig.
| Env var | Field | Default |
|---|---|---|
NESTRS_OPENTELEMETRY__SERVICE_NAME | service_name | argument to init |
NESTRS_OPENTELEMETRY__SERVICE_VERSION | service_version | None |
NESTRS_OPENTELEMETRY__SERVICE_ENVIRONMENT | deployment_environment | None |
NESTRS_OPENTELEMETRY__SERVICE_INSTANCE_ID | service_instance_id | fresh UUIDv7 per process |
NESTRS_OPENTELEMETRY__LOG_LEVEL | log_filter | info |
NESTRS_OPENTELEMETRY__LOG_FORMAT | log_format | text (json available) |
NESTRS_OPENTELEMETRY__LOG_SOURCE_LOCATION | log_source_location | false (on in scaffolded dev) |
NESTRS_OPENTELEMETRY__OTLP_ENDPOINT | otlp_endpoint | None (console-only) |
NESTRS_OPENTELEMETRY__SAMPLE_RATIO | trace_sample_ratio | 1.0 |
The OTLP exporter wires up only when otlp_endpoint is set. Without it,
the subscriber stays console-only; the W3C propagator is still installed so
incoming traceparent is parsed and outgoing headers carry one.
NESTRS_OPENTELEMETRY__OTLP_ENDPOINT=http://otel-collector:4318 \NESTRS_OPENTELEMETRY__SERVICE_VERSION=2026.06.0 \NESTRS_OPENTELEMETRY__SERVICE_ENVIRONMENT=production \NESTRS_OPENTELEMETRY__SAMPLE_RATIO=0.1 \RUST_LOG=info \ nestrs run start apiThe exporter uses HTTP/protobuf via reqwest (no tonic, no openssl) — the
batch processors export from a dedicated non-Tokio thread so the blocking
reqwest client is the right pick, the upstream-recommended pairing.
Feature flags
Section titled “Feature flags”nest-rs-opentelemetry ships two features:
otlp(default) — pulls theopentelemetry-*crates, installs the OTLP exporters for traces / metrics / logs, and bridgestracingevents into OTel logs. Without it, the subscriber stays a puretracing-subscribersetup — useful for a tiny binary that should not link the OTel SDK.http— pullspoem+nest-rs-http+nest-rs-middlewareso the per-request HTTP interceptor compiles. A headless app (a queue worker) usesotlpwithouthttpand stays free of the HTTP transport. Requiresotlp— the interceptor reads the trace id off the OTel span.
nest-rs-opentelemetry.workspace = truenest-rs-opentelemetry = { workspace = true, features = ["http"] }Boot-time safety
Section titled “Boot-time safety”thread 'main' panicked at 'OpenTelemetryModule was imported without calling`OpenTelemetry::init` first — the global tracer and meter are no-ops, sotraces and metrics would be silently dropped. Add `let _opentelemetry =nest_rs_opentelemetry::OpenTelemetry::init("<service>")?;` at the top of`main`, before building the app.'Forgetting OpenTelemetry::init is the failure mode that hurts in production:
the backend stays quiet, the team thinks observability is on, and the first
time someone needs a trace there’s nothing to grep. The boot guard makes it a
deterministic startup error instead — visible the first time you run the
binary locally.
use nest_rs_testing::TestApp;
#[tokio::test]async fn users_list_returns_a_page() { let app = TestApp::builder() .module::<ApiModule>() .with_test_telemetry() .build() .await .unwrap();
app.http().get("/users").send().await.assert_status_is_ok();}with_test_telemetry calls OpenTelemetry::init_for_tests — a console-only
init honouring RUST_LOG (default warn for noise control). It is
idempotent; the first test to run wins, the rest no-op. It is gated by the
opentelemetry feature on nest-rs-testing.
A test that does not import OpenTelemetryModule does not need it.
Going further
Section titled “Going further”- Per-request context — the request span carries
request_id,actor_id,tenant_idwhen an authn module is mounted. Layer your own fields withtracing::info!(target: "api::users", actor = %auth.sub, …). Server-Timing— the companionnest-rs-server-timingcrate adds aServer-Timingheader summarising the request’s main spans. Mount withServerTimingModule.- Module lifecycle —
tracing::info!onnest_rs::modulefires once per module when its providers register, so the boot trace shows your import tree in resolution order.
Reference
Section titled “Reference”crates/nest-rs-opentelemetry/—OpenTelemetry::init,OpenTelemetryConfig,OpenTelemetryModule,OpenTelemetryMeter,LogFormat.crates/nest-rs-server-timing/— theServer-Timingheader middleware.