Confirm the per-request root-span middleware works on actix-web 4 + otel 0.31 #136

Closed
opened 2026-09-29 14:57:06 +00:00 by cameron · 1 comment
Owner

Part of #135

Question

Which conventional Actix middleware gives us a per-request root span that (a) starts a tracing span for each HTTP request, (b) extracts an incoming W3C traceparent header so upstream traces connect, and (c) works on our actix-web version + opentelemetry 0.31.0?

Candidates:

  • opentelemetry-instrumentation-actix-web (RequestTracing) — the OTel-native middleware. Check the version that targets otel 0.31 and whether it integrates with tracing (or needs tracing-opentelemetry alongside).
  • tracing-actix-web — known dead for OTel (its OTel layer stopped at otel 0.16); determine whether its plain tracing middleware is still useful, or fully displaced.
  • Hand-rolled middleware — the fallback if no crate fits cleanly.

Deliverable

The recommended option, exact crate + version (or hand-rolled shape), whether it extracts traceparent and links to the OTLP tracer, and how handlers get the request span (#[instrument] or ambient). If the docs are ambiguous, a minimal compile-check prototype on a scratch branch is acceptable — link it as an asset.

Part of #135 ## Question Which conventional Actix middleware gives us a per-request root span that (a) starts a `tracing` span for each HTTP request, (b) extracts an incoming W3C `traceparent` header so upstream traces connect, and (c) works on our actix-web version + `opentelemetry` 0.31.0? Candidates: - `opentelemetry-instrumentation-actix-web` (`RequestTracing`) — the OTel-native middleware. Check the version that targets otel 0.31 and whether it integrates with `tracing` (or needs `tracing-opentelemetry` alongside). - `tracing-actix-web` — known dead for OTel (its OTel layer stopped at otel 0.16); determine whether its plain tracing middleware is still useful, or fully displaced. - Hand-rolled middleware — the fallback if no crate fits cleanly. ## Deliverable The recommended option, exact crate + version (or hand-rolled shape), whether it extracts `traceparent` and links to the OTLP tracer, and how handlers get the request span (`#[instrument]` or ambient). If the docs are ambiguous, a minimal compile-check prototype on a scratch branch is acceptable — link it as an asset.
Author
Owner

Resolution — use tracing-actix-web 0.7.25 with the opentelemetry_0_31 feature

Decision: tracing-actix-web = { version = "0.7.25", features = ["opentelemetry_0_31"] }, registered as TracingLogger in the actix App. No hand-rolled middleware needed.

Why it fits:

  • tracing-native. TracingLogger wraps each request in a tracing root span; every child span created during the request nests under it automatically. No OTel↔tracing impedance.
  • Extracts incoming W3C traceparent. set_otel_parent (its src/otel.rs) reads the request headers through the global text-map propagator and calls span.set_parent(parent_context), so the root span links to the upstream trace (and records trace_id).
  • Exact version match. The opentelemetry_0_31 feature pulls opentelemetry 0.31 + tracing-opentelemetry 0.32 — exactly the pair this repo needs. No OTel bump.
  • Actively maintained. Moved to actix/actix-extras; 0.7.25 is current and supports OTel through 0.32 via per-version feature flags.

Correction to a prior assumption. tracing-actix-web was earlier believed dead for OTel > 0.16 (its OTel layer "stopped at 0.16"). That is stale: OTel 0.31 support landed in PR #144, and the crate tracks OTel per feature flag. Confirmed against src/otel.rs + Cargo.toml on actix/actix-extras main.

Rejected alternative: opentelemetry-instrumentation-actix-web (RequestTracing). It emits raw OTel spans, not tracing spans, and its 0.24 line dropped its tracing dependency and targets otel 0.32. Handing the root span to the raw OTel tracer while children come from tracing would need a reverse bridge that doesn't exist, so it does not compose with a tracing-native migration. It would also pull a second otel version.

Required companion change (new fact for #138 and #139): the app currently never calls opentelemetry::global::set_text_map_propagator(...) — it constructs TraceContextPropagator::new() ad hoc in otel::extract_context_from_request. tracing-actix-web reads the global propagator, so startup must now call:

opentelemetry::global::set_text_map_propagator(
    opentelemetry_sdk::propagation::TraceContextPropagator::new(),
);

Without it, get_text_map_propagator returns the default no-op propagator and traceparent extraction silently no-ops.

Wiring:

  • Register TracingLogger::default() in the actix App (main.rs / service.rs).
  • The subscriber (#138) must include tracing_opentelemetry::OpenTelemetryLayer so the root span is exported and the parent set by set_otel_parent takes effect.
  • Handlers: the root span is ambient; use #[instrument] for named child spans or the RootSpan extractor to enrich it. The per-handler extract_context_from_request + tracer.start_with_context boilerplate is dropped.

Verdict: conventional, tracing-native, exact-version-fit; adopt it and close this ticket.

## Resolution — use `tracing-actix-web` 0.7.25 with the `opentelemetry_0_31` feature **Decision:** `tracing-actix-web = { version = "0.7.25", features = ["opentelemetry_0_31"] }`, registered as `TracingLogger` in the actix `App`. No hand-rolled middleware needed. **Why it fits:** - **tracing-native.** `TracingLogger` wraps each request in a `tracing` root span; every child span created during the request nests under it automatically. No OTel↔tracing impedance. - **Extracts incoming W3C `traceparent`.** `set_otel_parent` (its `src/otel.rs`) reads the request headers through the global text-map propagator and calls `span.set_parent(parent_context)`, so the root span links to the upstream trace (and records `trace_id`). - **Exact version match.** The `opentelemetry_0_31` feature pulls `opentelemetry 0.31` + `tracing-opentelemetry 0.32` — exactly the pair this repo needs. No OTel bump. - **Actively maintained.** Moved to `actix/actix-extras`; 0.7.25 is current and supports OTel through 0.32 via per-version feature flags. **Correction to a prior assumption.** `tracing-actix-web` was earlier believed dead for OTel > 0.16 (its OTel layer "stopped at 0.16"). That is stale: OTel 0.31 support landed in PR #144, and the crate tracks OTel per feature flag. Confirmed against `src/otel.rs` + `Cargo.toml` on `actix/actix-extras` main. **Rejected alternative: `opentelemetry-instrumentation-actix-web` (`RequestTracing`).** It emits raw **OTel** spans, not `tracing` spans, and its 0.24 line dropped its `tracing` dependency and targets otel 0.32. Handing the root span to the raw OTel tracer while children come from `tracing` would need a reverse bridge that doesn't exist, so it does not compose with a tracing-native migration. It would also pull a second otel version. **Required companion change (new fact for #138 and #139):** the app currently **never** calls `opentelemetry::global::set_text_map_propagator(...)` — it constructs `TraceContextPropagator::new()` ad hoc in `otel::extract_context_from_request`. `tracing-actix-web` reads the **global** propagator, so startup must now call: ```rust opentelemetry::global::set_text_map_propagator( opentelemetry_sdk::propagation::TraceContextPropagator::new(), ); ``` Without it, `get_text_map_propagator` returns the default no-op propagator and `traceparent` extraction silently no-ops. **Wiring:** - Register `TracingLogger::default()` in the actix `App` (`main.rs` / `service.rs`). - The subscriber (#138) must include `tracing_opentelemetry::OpenTelemetryLayer` so the root span is exported and the parent set by `set_otel_parent` takes effect. - Handlers: the root span is ambient; use `#[instrument]` for named child spans or the `RootSpan` extractor to enrich it. The per-handler `extract_context_from_request` + `tracer.start_with_context` boilerplate is dropped. **Verdict:** conventional, tracing-native, exact-version-fit; adopt it and close this ticket.
cameron added the wayfinder:research label 2026-09-30 00:47:34 +00:00
Sign in to join this conversation.