Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

- `UserSpan::deferred()` creates a stable request root that can be captured by telemetry contexts
before `activate()` makes the sampling decision.
- `TelemetryContext::with_user_span()` attaches an explicit `UserSpan` without changing ambient
scope; the returned context and handle share the same underlying span.
- A deferred root is an inactive span in a shared `RwLock`. Activation replaces it under the write
lock; the first active span wins, while inactive results remain retryable.
- Recording and children created before activation are inactive. Contexts that captured the root
Expand Down
28 changes: 23 additions & 5 deletions foundations/src/telemetry/tracing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,21 @@ impl UserSpan {
pub fn finish(self) {}
}

#[cfg(feature = "user-tracing")]
impl TelemetryContext {
/// Returns a copy of this context with `span` as its current user-tracing span.
///
/// The returned context and the handle share the same underlying span, so activating a
/// deferred handle updates work already carrying the context. Existing logging and internal
/// tracing state is preserved. Use [`TelemetryContext::scope`] or [`TelemetryContext::apply`]
/// to make the returned context active.
pub fn with_user_span(&self, span: &UserSpan) -> Self {
let mut ctx = self.clone();
ctx.user_span = Some(span.span.clone());
ctx
}
}

/// A span recorded in both the internal and user traces, produced by [`dual_span`].
///
/// Scope ends when the handle is dropped. [`into_context`](Self::into_context) carries both the
Expand Down Expand Up @@ -1831,9 +1846,12 @@ mod user_tracing_tests {
#[test]
fn deferred_handle_activates_context_captured_before_activation() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
let unrelated_ctx = TelemetryContext::test();
let root = UserSpan::deferred();
let request_ctx = root.enter().into_context();
let request_ctx = {
let _scope = unrelated_ctx.scope();
ctx.with_user_span(&root)
};
let tag_factory_called = Cell::new(false);

root.set_tags(|| {
Expand All @@ -1844,11 +1862,10 @@ mod user_tracing_tests {
assert!(!root.is_sampled());
assert!(root.w3c_traceparent().is_none());

root.activate("request", routing(), None);
assert!(root.is_sampled());

{
let _request = request_ctx.scope();
root.activate("request", routing(), None);
assert!(root.is_sampled());
user_tracing::add_span_tags!("after" => true);
let _child = user_tracing::span("child");
}
Expand All @@ -1869,6 +1886,7 @@ mod user_tracing_tests {
.contains(&("after".to_string(), TagValue::Boolean(true)))
);
assert!(!traces[0].0.tags.iter().any(|(name, _)| name == "before"));
assert!(unrelated_ctx.user_traces(Default::default()).is_empty());
}

#[test]
Expand Down
Loading