Otel: unify tracing subscriber across dev+release; env-gated OTLP export (#138)

Install ONE `tracing` subscriber in both build profiles via
`otel::init_tracing()` (previously release-only, so spans were a no-op in
dev). Behaviour is now environment-driven, not build-profile-driven:

- OTLP span export happens only when `OTLP_OTLS_ENDPOINT` is set
  (release / staging). Unset -> spans are still created with valid
  contexts (so outgoing `traceparent` injection keeps working) but export
  nowhere; the console `fmt` layer is what shows them.
- `tracing_subscriber::registry()` + `EnvFilter` (RUST_LOG) +
  `OpenTelemetryLayer` + console `fmt` layer, installed in both builds.
- Set the global W3C `TraceContextPropagator`: required by the
  `tracing-actix-web` request middleware (#139) and by the outgoing
  traceparent helpers, both of which read the *global* propagator.
- `tracing-log` bridge stays OFF (`default-features = false`) so the console
  fmt layer prints tracing spans only; `log` records keep their existing
  path (env_logger in debug, OTel logs in release) without double-printing.

Deps: + tracing, tracing-subscriber (env-filter/fmt/ansi), tracing-opentelemetry
0.32 (pins opentelemetry 0.31 + otel_sdk 0.31, no version bump); opentelemetry_sdk
gains the `testing` feature.

Add `tests/otel_propagation.rs`: the regression matrix from spike #133, pinning
the invariant the whole migration rests on -- an OTel context does NOT cross a
bare `tokio::spawn` on a multi-thread runtime (S2/S4 orphan onto a fresh trace)
but DOES with an explicit parent carry across the spawn (S6 `.instrument()`,
S7 `span.enter()`). This is why every spawn in the codebase must carry the parent.

clippy --all-targets clean; 690 lib + 775 integration + regression + 4 doc-tests pass.
This commit is contained in:
Cameron Cordes committed 2026-09-29 17:02:40 -04:00
1 parent 8284509c90
commit e93f6b6d69
5 files changed
+447 -14

No files matched your search

+4 -2
View File
@@ -111,7 +111,10 @@ fn main() -> std::io::Result<()> {
let system = actix::System::new();
system.block_on(async {
// Just use basic logger when running a non-release build
// One `tracing` subscriber in every build: env-driven, so dev prints
// spans to the console and release exports them over OTLP. The `log`
// pipeline stays separate — env_logger in debug, OTel logs in release.
otel::init_tracing();
#[cfg(debug_assertions)]
{
env_logger::init();
@@ -119,7 +122,6 @@ fn main() -> std::io::Result<()> {
#[cfg(not(debug_assertions))]
{
otel::init_logs();
otel::init_tracing();
}
// AppState construction loads (and seeds if needed) the libraries