Use set_parent instead of thread-local context for traceparent
Replaces attach_traceparent/TraceparentGuard with context_from_traceparent,
letting callers set the parent directly on the span via
OpenTelemetrySpanExt::set_parent rather than manipulating thread-local
OTEL context state.
https://claude.ai/code/session_01Tbgz29e8A9KS4Bh94skkFX
diff --git a/quire-ci/Cargo.toml b/quire-ci/Cargo.toml
index 5e7328a..a99f364 100644
--- a/quire-ci/Cargo.toml
+++ b/quire-ci/Cargo.toml
@@ -19,3 +19,4 @@ tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread"] }
tracing = { workspace = true }
+tracing-opentelemetry = { workspace = true }
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index 425050f..68d2cd1 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -18,6 +18,7 @@ use quire_core::ci::runtime::{Runtime, RuntimeError, RuntimeEvent, RuntimeHandle
use quire_core::fennel::FennelError;
use quire_core::secret::{Error as SecretError, Result as SecretResult, SecretRegistry};
use quire_core::telemetry::{self, FmtMode, MietteLayer};
+use tracing_opentelemetry::OpenTelemetrySpanExt as _;
use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
use std::sync::Arc;
@@ -364,14 +365,12 @@ fn main() -> Result<()> {
// before the Sentry client flushes to the server.
let _tracing_guard = telemetry::init_tracing(miette_layer, FmtMode::Plain)?;
- // Attach the remote trace context so quire-ci spans appear
- // as children of the orchestrator's span in Sentry.
- let _cx_guard = sentry_ctx
- .traceparent
- .as_deref()
- .map(telemetry::attach_traceparent);
- let _run_span =
- tracing::info_span!("quire.ci.run", sha = %meta.sha, r#ref = %meta.r#ref).entered();
+ let run_span =
+ tracing::info_span!("quire.ci.run", sha = %meta.sha, r#ref = %meta.r#ref);
+ if let Some(tp) = sentry_ctx.traceparent.as_deref() {
+ run_span.set_parent(telemetry::context_from_traceparent(tp));
+ }
+ let _run_span = run_span.entered();
let registry = SecretRegistry::new(move |name| client.fetch_secret(name));
diff --git a/quire-core/src/telemetry.rs b/quire-core/src/telemetry.rs
index 3d0be4b..e3d14df 100644
--- a/quire-core/src/telemetry.rs
+++ b/quire-core/src/telemetry.rs
@@ -6,6 +6,7 @@ use std::sync::Arc;
use miette::IntoDiagnostic;
use opentelemetry::propagation::TextMapPropagator as _;
use opentelemetry::trace::TracerProvider as _;
+use tracing_opentelemetry::OpenTelemetrySpanExt as _;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
@@ -200,10 +201,6 @@ impl Drop for TracingGuard {
}
}
-/// Opaque guard that restores the previous OTEL context on drop.
-#[allow(dead_code)]
-pub struct TraceparentGuard(opentelemetry::ContextGuard);
-
/// Extract the W3C traceparent for the currently active tracing span.
/// Returns None when no OTEL span is active (e.g. no DSN, not yet entered a span).
pub fn current_traceparent() -> Option<String> {
@@ -214,14 +211,13 @@ pub fn current_traceparent() -> Option<String> {
carrier.remove("traceparent")
}
-/// Inject a W3C traceparent into the current thread's OTEL context.
-/// The returned guard restores the previous context on drop.
-pub fn attach_traceparent(traceparent: &str) -> TraceparentGuard {
+/// Decode a W3C traceparent string into an OTEL context suitable for
+/// passing to [`tracing_opentelemetry::OpenTelemetrySpanExt::set_parent`].
+pub fn context_from_traceparent(traceparent: &str) -> opentelemetry::Context {
let propagator = opentelemetry_sdk::propagation::TraceContextPropagator::new();
let mut carrier = std::collections::HashMap::new();
carrier.insert("traceparent".to_string(), traceparent.to_string());
- let cx = propagator.extract(&carrier);
- TraceparentGuard(cx.attach())
+ propagator.extract(&carrier)
}
/// Initialize the global tracing subscriber with `QUIRE_LOG`-driven filtering,