opentelemetry/context.rs
1//! Execution-scoped context propagation.
2//!
3//! The `context` module provides mechanisms for propagating values across API boundaries and between
4//! logically associated execution units. It enables cross-cutting concerns to access their data in-process
5//! using a shared context object.
6//!
7//! # Main Types
8//!
9//! - [`Context`]: An immutable, execution-scoped collection of values.
10//!
11
12use crate::otel_warn;
13#[cfg(feature = "trace")]
14use crate::trace::context::SynchronizedSpan;
15use std::any::{Any, TypeId};
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::fmt;
19use std::hash::{BuildHasherDefault, Hasher};
20use std::marker::PhantomData;
21use std::sync::Arc;
22#[cfg(feature = "experimental_context_observer")]
23use std::sync::OnceLock;
24
25#[cfg(feature = "futures")]
26mod future_ext;
27
28#[cfg(feature = "futures")]
29pub use future_ext::{FutureExt, WithContext};
30
31#[cfg(feature = "experimental_context_observer")]
32pub use context_observer::*;
33
34thread_local! {
35 static CURRENT_CONTEXT: RefCell<ContextStack> = RefCell::new(ContextStack::default());
36}
37
38/// An execution-scoped collection of values.
39///
40/// A [`Context`] is a propagation mechanism which carries execution-scoped
41/// values across API boundaries and between logically associated execution
42/// units. Cross-cutting concerns access their data in-process using the same
43/// shared context object.
44///
45/// [`Context`]s are immutable, and their write operations result in the creation
46/// of a new context containing the original values and the new specified values.
47///
48/// ## Context state
49///
50/// Concerns can create and retrieve their local state in the current execution
51/// state represented by a context through the [`get`] and [`with_value`]
52/// methods. It is recommended to use application-specific types when storing new
53/// context values to avoid unintentionally overwriting existing state.
54///
55/// ## Managing the current context
56///
57/// Contexts can be associated with the caller's current execution unit on a
58/// given thread via the [`attach`] method, and previous contexts can be restored
59/// by dropping the returned [`ContextGuard`]. Context can be nested, and will
60/// restore their parent outer context when detached on drop. To access the
61/// values of the context, a snapshot can be created via the [`Context::current`]
62/// method.
63///
64/// [`Context::current`]: Context::current()
65/// [`get`]: Context::get()
66/// [`with_value`]: Context::with_value()
67/// [`attach`]: Context::attach()
68///
69/// # Examples
70///
71/// ```
72/// use opentelemetry::Context;
73///
74/// // Application-specific `a` and `b` values
75/// #[derive(Debug, PartialEq)]
76/// struct ValueA(&'static str);
77/// #[derive(Debug, PartialEq)]
78/// struct ValueB(u64);
79///
80/// let _outer_guard = Context::new().with_value(ValueA("a")).attach();
81///
82/// // Only value a has been set
83/// let current = Context::current();
84/// assert_eq!(current.get::<ValueA>(), Some(&ValueA("a")));
85/// assert_eq!(current.get::<ValueB>(), None);
86///
87/// {
88/// let _inner_guard = Context::current_with_value(ValueB(42)).attach();
89/// // Both values are set in inner context
90/// let current = Context::current();
91/// assert_eq!(current.get::<ValueA>(), Some(&ValueA("a")));
92/// assert_eq!(current.get::<ValueB>(), Some(&ValueB(42)));
93/// }
94///
95/// // Resets to only the `a` value when inner guard is dropped
96/// let current = Context::current();
97/// assert_eq!(current.get::<ValueA>(), Some(&ValueA("a")));
98/// assert_eq!(current.get::<ValueB>(), None);
99/// ```
100#[derive(Clone, Default)]
101pub struct Context {
102 #[cfg(feature = "trace")]
103 pub(crate) span: Option<Arc<SynchronizedSpan>>,
104 entries: Option<Arc<EntryMap>>,
105 suppress_telemetry: bool,
106 /// A context's observer view of this [Context].
107 ///
108 /// The main application for observers is to share information from the current context with
109 /// external readers (e.g. a full host eBPF profiler). In that case, the observer will attach
110 /// and detach some data on context events using its own mechanism. The shared data has no
111 /// reason to match our internal representation ([Context]).
112 ///
113 /// [observer_view] makes it possible to store the observer's view of the context alongside a
114 /// [Context] value, such that it can be attached and detached on context events.
115 #[cfg(feature = "experimental_context_observer")]
116 observer_view: Option<Arc<dyn ObserverContextView>>,
117}
118
119type EntryMap = HashMap<TypeId, Arc<dyn Any + Sync + Send>, BuildHasherDefault<IdHasher>>;
120
121impl Context {
122 /// Creates an empty `Context`.
123 ///
124 /// The context is initially created with a capacity of 0, so it will not
125 /// allocate. Use [`with_value`] to create a new context that has entries.
126 ///
127 /// [`with_value`]: Context::with_value()
128 pub fn new() -> Self {
129 Context::default()
130 }
131
132 /// Returns an immutable snapshot of the current thread's context.
133 ///
134 /// # Behavior During Context Drop
135 ///
136 /// When called from within a [`Drop`] implementation that is triggered by
137 /// a [`ContextGuard`] being dropped (e.g., when a [`Span`] is dropped as part
138 /// of context cleanup), this function returns **whatever context happens to be
139 /// current after the guard is popped**, not the context being dropped.
140 ///
141 /// **Important**: The returned context may be completely unrelated to the
142 /// context being dropped, as contexts can be activated in any order and are
143 /// not necessarily hierarchical. Do not rely on any relationship between them.
144 ///
145 /// This behavior is by design and prevents panics that would otherwise occur
146 /// from attempting to borrow the context while it's being mutably borrowed
147 /// for cleanup. See [issue #2871](https://github.com/open-telemetry/opentelemetry-rust/issues/2871)
148 /// for details.
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use opentelemetry::Context;
154 ///
155 /// #[derive(Debug, PartialEq)]
156 /// struct ValueA(&'static str);
157 ///
158 /// fn do_work() {
159 /// assert_eq!(Context::current().get(), Some(&ValueA("a")));
160 /// }
161 ///
162 /// let _guard = Context::new().with_value(ValueA("a")).attach();
163 /// do_work()
164 /// ```
165 ///
166 /// [`Span`]: crate::trace::Span
167 pub fn current() -> Self {
168 Self::map_current(|cx| cx.clone())
169 }
170
171 /// Applies a function to the current context returning its value.
172 ///
173 /// This can be used to build higher performing algebraic expressions for
174 /// optionally creating a new context without the overhead of cloning the
175 /// current one and dropping it.
176 ///
177 /// Note: This function will panic if you attempt to attach another context
178 /// while the current one is still borrowed.
179 pub fn map_current<T>(f: impl FnOnce(&Context) -> T) -> T {
180 CURRENT_CONTEXT.with(|cx| cx.borrow().map_current_cx(f))
181 }
182
183 /// Returns a clone of the current thread's context with the given value.
184 ///
185 /// This is a more efficient form of `Context::current().with_value(value)`
186 /// as it avoids the intermediate context clone.
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// use opentelemetry::Context;
192 ///
193 /// // Given some value types defined in your application
194 /// #[derive(Debug, PartialEq)]
195 /// struct ValueA(&'static str);
196 /// #[derive(Debug, PartialEq)]
197 /// struct ValueB(u64);
198 ///
199 /// // You can create and attach context with the first value set to "a"
200 /// let _guard = Context::new().with_value(ValueA("a")).attach();
201 ///
202 /// // And create another context based on the fist with a new value
203 /// let all_current_and_b = Context::current_with_value(ValueB(42));
204 ///
205 /// // The second context now contains all the current values and the addition
206 /// assert_eq!(all_current_and_b.get::<ValueA>(), Some(&ValueA("a")));
207 /// assert_eq!(all_current_and_b.get::<ValueB>(), Some(&ValueB(42)));
208 /// ```
209 pub fn current_with_value<T: 'static + Send + Sync>(value: T) -> Self {
210 Self::map_current(|cx| cx.with_value(value))
211 }
212
213 /// Returns a reference to the entry for the corresponding value type.
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// use opentelemetry::Context;
219 ///
220 /// // Given some value types defined in your application
221 /// #[derive(Debug, PartialEq)]
222 /// struct ValueA(&'static str);
223 /// #[derive(Debug, PartialEq)]
224 /// struct MyUser();
225 ///
226 /// let cx = Context::new().with_value(ValueA("a"));
227 ///
228 /// // Values can be queried by type
229 /// assert_eq!(cx.get::<ValueA>(), Some(&ValueA("a")));
230 ///
231 /// // And return none if not yet set
232 /// assert_eq!(cx.get::<MyUser>(), None);
233 /// ```
234 pub fn get<T: 'static>(&self) -> Option<&T> {
235 self.entries
236 .as_ref()?
237 .get(&TypeId::of::<T>())?
238 .downcast_ref()
239 }
240
241 /// Returns a copy of the context with the new value included.
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// use opentelemetry::Context;
247 ///
248 /// // Given some value types defined in your application
249 /// #[derive(Debug, PartialEq)]
250 /// struct ValueA(&'static str);
251 /// #[derive(Debug, PartialEq)]
252 /// struct ValueB(u64);
253 ///
254 /// // You can create a context with the first value set to "a"
255 /// let cx_with_a = Context::new().with_value(ValueA("a"));
256 ///
257 /// // And create another context based on the fist with a new value
258 /// let cx_with_a_and_b = cx_with_a.with_value(ValueB(42));
259 ///
260 /// // The first context is still available and unmodified
261 /// assert_eq!(cx_with_a.get::<ValueA>(), Some(&ValueA("a")));
262 /// assert_eq!(cx_with_a.get::<ValueB>(), None);
263 ///
264 /// // The second context now contains both values
265 /// assert_eq!(cx_with_a_and_b.get::<ValueA>(), Some(&ValueA("a")));
266 /// assert_eq!(cx_with_a_and_b.get::<ValueB>(), Some(&ValueB(42)));
267 /// ```
268 pub fn with_value<T: 'static + Send + Sync>(&self, value: T) -> Self {
269 let entries = if let Some(current_entries) = &self.entries {
270 let mut inner_entries = (**current_entries).clone();
271 inner_entries.insert(TypeId::of::<T>(), Arc::new(value));
272 Some(Arc::new(inner_entries))
273 } else {
274 let mut entries = EntryMap::default();
275 entries.insert(TypeId::of::<T>(), Arc::new(value));
276 Some(Arc::new(entries))
277 };
278
279 Context {
280 entries,
281 #[cfg(feature = "trace")]
282 span: self.span.clone(),
283 suppress_telemetry: self.suppress_telemetry,
284 #[cfg(feature = "experimental_context_observer")]
285 observer_view: None,
286 }
287 .with_observer_view()
288 }
289
290 /// Replaces the current context on this thread with this context.
291 ///
292 /// Dropping the returned [`ContextGuard`] will reset the current context to the
293 /// previous value.
294 ///
295 ///
296 /// # Examples
297 ///
298 /// ```
299 /// use opentelemetry::Context;
300 ///
301 /// #[derive(Debug, PartialEq)]
302 /// struct ValueA(&'static str);
303 ///
304 /// let my_cx = Context::new().with_value(ValueA("a"));
305 ///
306 /// // Set the current thread context
307 /// let cx_guard = my_cx.attach();
308 /// assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA("a")));
309 ///
310 /// // Drop the guard to restore the previous context
311 /// drop(cx_guard);
312 /// assert_eq!(Context::current().get::<ValueA>(), None);
313 /// ```
314 ///
315 /// Guards do not need to be explicitly dropped:
316 ///
317 /// ```
318 /// use opentelemetry::Context;
319 ///
320 /// #[derive(Debug, PartialEq)]
321 /// struct ValueA(&'static str);
322 ///
323 /// fn my_function() -> String {
324 /// // attach a context the duration of this function.
325 /// let my_cx = Context::new().with_value(ValueA("a"));
326 /// // NOTE: a variable name after the underscore is **required** or rust
327 /// // will drop the guard, restoring the previous context _immediately_.
328 /// let _guard = my_cx.attach();
329 ///
330 /// // anything happening in functions we call can still access my_cx...
331 /// my_other_function();
332 ///
333 /// // returning from the function drops the guard, exiting the span.
334 /// return "Hello world".to_owned();
335 /// }
336 ///
337 /// fn my_other_function() {
338 /// // ...
339 /// }
340 /// ```
341 /// Sub-scopes may be created to limit the duration for which the span is
342 /// entered:
343 ///
344 /// ```
345 /// use opentelemetry::Context;
346 ///
347 /// #[derive(Debug, PartialEq)]
348 /// struct ValueA(&'static str);
349 ///
350 /// let my_cx = Context::new().with_value(ValueA("a"));
351 ///
352 /// {
353 /// let _guard = my_cx.attach();
354 ///
355 /// // the current context can access variables in
356 /// assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA("a")));
357 ///
358 /// // exiting the scope drops the guard, detaching the context.
359 /// }
360 ///
361 /// // this is back in the default empty context
362 /// assert_eq!(Context::current().get::<ValueA>(), None);
363 /// ```
364 pub fn attach(self) -> ContextGuard {
365 let cx_id = CURRENT_CONTEXT.with(|cx| cx.borrow_mut().push(self));
366
367 ContextGuard {
368 cx_pos: cx_id,
369 _marker: PhantomData,
370 }
371 }
372
373 /// Returns whether telemetry is suppressed in this context.
374 #[inline]
375 pub fn is_telemetry_suppressed(&self) -> bool {
376 self.suppress_telemetry
377 }
378
379 /// Returns a new context with telemetry suppression enabled.
380 pub fn with_telemetry_suppressed(&self) -> Self {
381 Context {
382 entries: self.entries.clone(),
383 #[cfg(feature = "trace")]
384 span: self.span.clone(),
385 suppress_telemetry: true,
386 #[cfg(feature = "experimental_context_observer")]
387 observer_view: None,
388 }
389 .with_observer_view()
390 }
391
392 /// Enters a scope where telemetry is suppressed.
393 ///
394 /// This method is specifically designed for OpenTelemetry components (like Exporters,
395 /// Processors etc.) to prevent generating recursive or self-referential
396 /// telemetry data when performing their own operations.
397 ///
398 /// Without suppression, we have a telemetry-induced-telemetry situation
399 /// where, operations like exporting telemetry could generate new telemetry
400 /// about the export process itself, potentially causing:
401 /// - Infinite telemetry feedback loops
402 /// - Excessive resource consumption
403 ///
404 /// This method:
405 /// 1. Takes the current context
406 /// 2. Creates a new context from current, with `suppress_telemetry` set to `true`
407 /// 3. Attaches it to the current thread
408 /// 4. Returns a guard that restores the previous context when dropped
409 ///
410 /// OTel SDK components would check `is_current_telemetry_suppressed()` before
411 /// generating new telemetry, but not end users.
412 ///
413 /// # Examples
414 ///
415 /// ```
416 /// use opentelemetry::Context;
417 ///
418 /// // Example: Inside an exporter's implementation
419 /// fn example_export_function() {
420 /// // Prevent telemetry-generating operations from creating more telemetry
421 /// let _guard = Context::enter_telemetry_suppressed_scope();
422 ///
423 /// // Verify suppression is active
424 /// assert_eq!(Context::is_current_telemetry_suppressed(), true);
425 ///
426 /// // Here you would normally perform operations that might generate telemetry
427 /// // but now they won't because the context has suppression enabled
428 /// }
429 ///
430 /// // Demonstrate the function
431 /// example_export_function();
432 /// ```
433 pub fn enter_telemetry_suppressed_scope() -> ContextGuard {
434 Self::map_current(|cx| cx.with_telemetry_suppressed()).attach()
435 }
436
437 /// Returns whether telemetry is suppressed in the current context.
438 ///
439 /// This method is used by OpenTelemetry components to determine whether they should
440 /// generate new telemetry in the current execution context. It provides a performant
441 /// way to check the suppression state.
442 ///
443 /// End-users generally should not use this method directly, as it is primarily intended for
444 /// OpenTelemetry SDK components.
445 ///
446 ///
447 #[inline]
448 pub fn is_current_telemetry_suppressed() -> bool {
449 Self::map_current(|cx| cx.is_telemetry_suppressed())
450 }
451
452 // Initialize `self.observer_view` for a newly created context using the current context
453 // observer, if set.
454 #[cfg(feature = "experimental_context_observer")]
455 fn with_observer_view(mut self) -> Self {
456 if let Some(observer) = GlobalContextObserver::get() {
457 self.observer_view = observer.make_view(&self);
458 }
459
460 self
461 }
462
463 // No-op when the experimental_context_observer feature is not enabled.
464 #[cfg(not(feature = "experimental_context_observer"))]
465 const fn with_observer_view(self) -> Self {
466 self
467 }
468
469 /// Returns the observer's view of this context, if any.
470 #[cfg(feature = "experimental_context_observer")]
471 #[inline]
472 pub fn observer_view(&self) -> &Option<Arc<dyn ObserverContextView>> {
473 &self.observer_view
474 }
475
476 #[cfg(feature = "trace")]
477 pub(crate) fn current_with_synchronized_span(value: SynchronizedSpan) -> Self {
478 Self::map_current(|cx| {
479 Context {
480 span: Some(Arc::new(value)),
481 entries: cx.entries.clone(),
482 suppress_telemetry: cx.suppress_telemetry,
483 #[cfg(feature = "experimental_context_observer")]
484 observer_view: None,
485 }
486 .with_observer_view()
487 })
488 }
489
490 #[cfg(feature = "trace")]
491 pub(crate) fn with_synchronized_span(&self, value: SynchronizedSpan) -> Self {
492 Context {
493 span: Some(Arc::new(value)),
494 entries: self.entries.clone(),
495 suppress_telemetry: self.suppress_telemetry,
496 #[cfg(feature = "experimental_context_observer")]
497 observer_view: None,
498 }
499 .with_observer_view()
500 }
501}
502
503impl fmt::Debug for Context {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 let mut dbg = f.debug_struct("Context");
506
507 #[cfg(feature = "trace")]
508 let mut entries = self.entries.as_ref().map_or(0, |e| e.len());
509 #[cfg(feature = "trace")]
510 {
511 if let Some(span) = &self.span {
512 dbg.field("span", &span.span_context());
513 entries += 1;
514 } else {
515 dbg.field("span", &"None");
516 }
517 }
518 #[cfg(not(feature = "trace"))]
519 let entries = self.entries.as_ref().map_or(0, |e| e.len());
520
521 dbg.field("entries count", &entries)
522 .field("suppress_telemetry", &self.suppress_telemetry)
523 .finish()
524 }
525}
526
527/// A guard that resets the current context to the prior context when dropped.
528#[derive(Debug)]
529pub struct ContextGuard {
530 // The position of the context in the stack. This is used to pop the context.
531 cx_pos: u16,
532 // Ensure this type is !Send as it relies on thread locals
533 _marker: PhantomData<*const ()>,
534}
535
536impl Drop for ContextGuard {
537 fn drop(&mut self) {
538 let id = self.cx_pos;
539 if id > ContextStack::BASE_POS && id < ContextStack::MAX_POS {
540 // Defer destructors that may call back into Context until after
541 // the mutable borrow of CURRENT_CONTEXT has been released.
542 let _to_drop =
543 CURRENT_CONTEXT.with(|context_stack| context_stack.borrow_mut().pop_id(id));
544 }
545 }
546}
547
548/// With TypeIds as keys, there's no need to hash them. They are already hashes
549/// themselves, coming from the compiler. The IdHasher holds the u64 of
550/// the TypeId, and then returns it, instead of doing any bit fiddling.
551#[derive(Clone, Default, Debug)]
552struct IdHasher(u64);
553
554impl Hasher for IdHasher {
555 fn write(&mut self, _: &[u8]) {
556 unreachable!("TypeId calls write_u64");
557 }
558
559 #[inline]
560 fn write_u64(&mut self, id: u64) {
561 self.0 = id;
562 }
563
564 #[inline]
565 fn finish(&self) -> u64 {
566 self.0
567 }
568}
569
570/// A stack for keeping track of the [`Context`] instances that have been attached
571/// to a thread.
572///
573/// The stack allows for popping of contexts by position, which is used to do out
574/// of order dropping of [`ContextGuard`] instances. Only when the top of the
575/// stack is popped, the topmost [`Context`] is actually restored.
576///
577/// The stack relies on the fact that it is thread local and that the
578/// [`ContextGuard`] instances that are constructed using ids from it can't be
579/// moved to other threads. That means that the ids are always valid and that
580/// they are always within the bounds of the stack.
581struct ContextStack {
582 /// This is the current [`Context`] that is active on this thread, and the top
583 /// of the [`ContextStack`]. It is always present, and if the `stack` is empty
584 /// it's an empty [`Context`].
585 ///
586 /// Having this here allows for fast access to the current [`Context`].
587 current_cx: Context,
588 /// A `stack` of the other contexts that have been attached to the thread.
589 stack: Vec<Option<Context>>,
590 /// Ensure this type is !Send as it relies on thread locals
591 _marker: PhantomData<*const ()>,
592}
593
594// Fields whose destructors must run after releasing the mutable
595// borrow of CURRENT_CONTEXT.
596#[derive(Default)]
597struct ContextToDrop {
598 #[cfg(feature = "trace")]
599 _span: Option<Arc<SynchronizedSpan>>,
600 _entries: Option<Arc<EntryMap>>,
601 #[cfg(feature = "experimental_context_observer")]
602 _observer_view: Option<Arc<dyn ObserverContextView>>,
603}
604
605impl From<Context> for ContextToDrop {
606 fn from(context: Context) -> Self {
607 Self {
608 #[cfg(feature = "trace")]
609 _span: context.span,
610 _entries: context.entries,
611 #[cfg(feature = "experimental_context_observer")]
612 _observer_view: context.observer_view,
613 }
614 }
615}
616
617impl ContextStack {
618 const BASE_POS: u16 = 0;
619 const MAX_POS: u16 = u16::MAX;
620 const INITIAL_CAPACITY: usize = 8;
621
622 #[inline(always)]
623 fn push(&mut self, cx: Context) -> u16 {
624 // The next id is the length of the `stack`, plus one since we have the
625 // top of the [`ContextStack`] as the `current_cx`.
626 let next_id = self.stack.len() + 1;
627 if next_id < ContextStack::MAX_POS.into() {
628 #[cfg(feature = "experimental_context_observer")]
629 if let Some(observer) = GlobalContextObserver::get() {
630 observer.on_context_enter(&self.current_cx, &cx);
631 }
632
633 let current_cx = std::mem::replace(&mut self.current_cx, cx);
634 self.stack.push(Some(current_cx));
635 next_id as u16
636 } else {
637 // This is an overflow, log it and ignore it.
638 otel_warn!(
639 name: "Context.AttachFailed",
640 message = format!("Too many contexts. Max limit is {}. \
641 Context::current() remains unchanged as this attach failed. \
642 Dropping the returned ContextGuard will have no impact on Context::current().",
643 ContextStack::MAX_POS)
644 );
645 ContextStack::MAX_POS
646 }
647 }
648
649 #[inline(always)]
650 fn pop_id(&mut self, pos: u16) -> ContextToDrop {
651 if pos == ContextStack::BASE_POS || pos == ContextStack::MAX_POS {
652 // The empty context is always at the bottom of the [`ContextStack`]
653 // and cannot be popped, and the overflow position is invalid, so do
654 // nothing.
655 otel_warn!(
656 name: "Context.OutOfOrderDrop",
657 position = pos,
658 message = if pos == ContextStack::BASE_POS {
659 "Attempted to pop the base context which is not allowed"
660 } else {
661 "Attempted to pop the overflow position which is not allowed"
662 }
663 );
664
665 return ContextToDrop::default();
666 }
667 let len: u16 = self.stack.len() as u16;
668 // Are we at the top of the [`ContextStack`]?
669 if pos == len {
670 // Shrink the stack if possible to clear out any out of order pops.
671 while let Some(None) = self.stack.last() {
672 _ = self.stack.pop();
673 }
674 // Restore the previous context. This will always happen since the
675 // empty context is always at the bottom of the stack if the
676 // [`ContextStack`] is not empty.
677 if let Some(Some(next_cx)) = self.stack.pop() {
678 #[cfg(feature = "experimental_context_observer")]
679 if let Some(observer) = GlobalContextObserver::get() {
680 observer.on_context_exit(&self.current_cx, &next_cx);
681 }
682
683 // Move fields with potentially reentrant destructors out of the borrow.
684 let old_cx = std::mem::replace(&mut self.current_cx, next_cx);
685 return old_cx.into();
686 }
687
688 ContextToDrop::default()
689 } else {
690 // This is an out of order pop.
691 if pos >= len {
692 // This is an invalid id, ignore it.
693 otel_warn!(
694 name: "Context.PopOutOfBounds",
695 position = pos,
696 stack_length = len,
697 message = "Attempted to pop beyond the end of the context stack"
698 );
699 return ContextToDrop::default();
700 }
701 // Remove the context and defer dropping its owned fields.
702 self.stack[pos as usize]
703 .take()
704 .map(ContextToDrop::from)
705 .unwrap_or_default()
706 }
707 }
708
709 #[inline(always)]
710 fn map_current_cx<T>(&self, f: impl FnOnce(&Context) -> T) -> T {
711 f(&self.current_cx)
712 }
713}
714
715impl Default for ContextStack {
716 fn default() -> Self {
717 ContextStack {
718 current_cx: Context::default(),
719 stack: Vec::with_capacity(ContextStack::INITIAL_CAPACITY),
720 _marker: PhantomData,
721 }
722 }
723}
724
725#[cfg(feature = "experimental_context_observer")]
726mod context_observer {
727 use super::*;
728
729 /// An observer trait for monitoring context transitions.
730 ///
731 /// Implementors of this trait can observe when a context is entered or exited, allowing for
732 /// custom logic to be executed during context switches.
733 ///
734 /// An observer-specific view can be attached to a [`Context`] via
735 /// [`Context::observer_view`]. A view is usually a different representation of the context
736 /// to be published through an alternative channel, but in essence it is arbitrary data computed
737 /// from the context.
738 ///
739 /// # Refcell already borrowed panic
740 ///
741 /// [crate::context] maintains thread-local, internal state in a `RefCell`. This implementation
742 /// detail leaks when the `experimental_context_observer` feature is enabled:
743 /// [Self::on_context_enter] and [Self::on_context_exit] are called from a point where the cell
744 /// is currently borrowed. If a [ContextObserver] implementation calls back into a
745 /// cell-borrowing function from the Context API, typically `Context::current()`, this will
746 /// result in a `Refcell already borrowed` panic.
747 ///
748 /// As general hygiene, with respect to the [Context] API, it is strongly advised to only use
749 /// simple getters and setters on the `from` and `to` context objects provided to the observer.
750 /// Do not call `Context::current()`, nor try to manually attach or detach context objects from
751 /// an observer.
752 ///
753 /// If you get Refcell-related panics within the [crate::context] module, this is the most
754 /// likely cause.
755 ///
756 /// # Example
757 ///
758 /// ```
759 /// # #[cfg(feature = "experimental_context_observer")]
760 /// # {
761 /// use opentelemetry::Context;
762 /// use opentelemetry::context::{ContextObserver, ObserverContextView};
763 /// use std::any::Any;
764 /// use std::sync::Arc;
765 ///
766 /// // An observer's own view of the context, carrying arbitrary data.
767 /// struct MyView {
768 /// correlation_id: u64,
769 /// }
770 ///
771 /// impl ObserverContextView for MyView {
772 /// fn as_any(&self) -> &dyn Any {
773 /// self
774 /// }
775 /// }
776 ///
777 /// struct Observer;
778 ///
779 /// # fn do_something(view: &MyView) { }
780 ///
781 /// impl ContextObserver for Observer {
782 /// fn on_context_enter(&self, from: &Context, to: &Context) {
783 /// let view = to.observer_view().as_ref().unwrap();
784 /// do_something(view.as_any().downcast_ref::<MyView>().unwrap());
785 /// }
786 ///
787 /// fn on_context_exit(&self, from: &Context, to: &Context) {
788 /// let view = to
789 /// .observer_view()
790 /// .as_ref()
791 /// .unwrap()
792 /// .as_any()
793 /// .downcast_ref::<MyView>()
794 /// .unwrap();
795 /// assert_eq!(view.correlation_id, 42);
796 /// }
797 ///
798 /// fn make_view(&self, ctx: &Context) -> Option<Arc<dyn ObserverContextView>> {
799 /// Some(Arc::new(MyView { correlation_id: 42 }))
800 /// }
801 /// }
802 /// # }
803 /// ```
804 pub trait ContextObserver {
805 /// Called when a context is entered, allowing observers to react to the transition
806 /// from one context (`from`) to another (`to`).
807 fn on_context_enter(&self, from: &Context, to: &Context);
808
809 /// Called when a context is exited, allowing observers to react to the transition
810 /// from one context (`from`) to another (`to`).
811 fn on_context_exit(&self, from: &Context, to: &Context);
812
813 /// Derive a fresh view for a newly created context. The provided `ctx` has an unitialized
814 /// view sets to `None`.
815 ///
816 /// The default implementation returns `None` for observers that don't use the view.
817 fn make_view(&self, _ctx: &Context) -> Option<Arc<dyn ObserverContextView>> {
818 None
819 }
820 }
821
822 static GLOBAL_CONTEXT_OBSERVER: OnceLock<Arc<dyn ContextObserver + Send + Sync>> =
823 OnceLock::new();
824
825 /// A global observer for context transitions.
826 ///
827 /// This struct provides static methods to set and get a global context observer.
828 #[derive(Debug)]
829 pub struct GlobalContextObserver;
830
831 impl GlobalContextObserver {
832 /// Sets the global context observer, logging a warning if it was already set.
833 pub fn set(observer: Arc<dyn ContextObserver + Send + Sync>) {
834 if GLOBAL_CONTEXT_OBSERVER.set(observer).is_err() {
835 otel_warn!(
836 name: "GlobalContextObserver.SetFailed",
837 message = "Global context observer was already set. Ignoring new observer."
838 );
839 }
840 }
841
842 /// Returns the global context observer.
843 pub(super) fn get<'a>() -> Option<&'a Arc<dyn ContextObserver + Send + Sync>> {
844 GLOBAL_CONTEXT_OBSERVER.get()
845 }
846 }
847
848 /// A context's observer view of a [Context]. This is an arbitrary data structure. See
849 /// [ContextObserver]'s documentation for examples.
850 #[cfg(feature = "experimental_context_observer")]
851 pub trait ObserverContextView: Any + Send + Sync {
852 /// Returns this view as a `&dyn Any`, enabling downcasting back to the concrete type.
853 ///
854 /// This method is required because this crate's MSRV is below the Rust version (1.86) that
855 /// stabilized trait upcasting. Without it, callers below 1.86 couldn't upcast `&dyn
856 /// ObserverContextView` to `&dyn Any` to perform the downcast themselves.
857 ///
858 /// This is a work-around for the absence of trait upcasting before Rust 1.86.
859 ///
860 /// Implementors should simply return `self`:
861 ///
862 /// ```ignore
863 /// fn as_any(&self) -> &dyn std::any::Any {
864 /// self
865 /// }
866 /// ```
867 fn as_any(&self) -> &dyn Any;
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874 use std::time::Duration;
875 use tokio::time::sleep;
876
877 #[derive(Debug, PartialEq)]
878 struct ValueA(u64);
879 #[derive(Debug, PartialEq)]
880 struct ValueB(u64);
881
882 struct ReadCurrentContextOnDrop;
883
884 impl Drop for ReadCurrentContextOnDrop {
885 fn drop(&mut self) {
886 let _ = Context::current();
887 }
888 }
889
890 #[test]
891 fn context_value_drop_can_access_current_context() {
892 let guard = Context::new().with_value(ReadCurrentContextOnDrop).attach();
893
894 drop(guard);
895 }
896
897 #[test]
898 fn out_of_order_context_value_drop_can_access_current_context() {
899 let outer_guard = Context::new().with_value(ReadCurrentContextOnDrop).attach();
900 let inner_guard = Context::new().attach();
901
902 drop(outer_guard);
903 drop(inner_guard);
904 }
905
906 #[test]
907 fn context_immutable() {
908 // start with Current, which should be an empty context
909 let cx = Context::current();
910 assert_eq!(cx.get::<ValueA>(), None);
911 assert_eq!(cx.get::<ValueB>(), None);
912
913 // with_value should return a new context,
914 // leaving the original context unchanged
915 let cx_new = cx.with_value(ValueA(1));
916
917 // cx should be unchanged
918 assert_eq!(cx.get::<ValueA>(), None);
919 assert_eq!(cx.get::<ValueB>(), None);
920
921 // cx_new should contain the new value
922 assert_eq!(cx_new.get::<ValueA>(), Some(&ValueA(1)));
923
924 // cx_new should be unchanged
925 let cx_newer = cx_new.with_value(ValueB(1));
926
927 // Cx and cx_new are unchanged
928 assert_eq!(cx.get::<ValueA>(), None);
929 assert_eq!(cx.get::<ValueB>(), None);
930 assert_eq!(cx_new.get::<ValueA>(), Some(&ValueA(1)));
931 assert_eq!(cx_new.get::<ValueB>(), None);
932
933 // cx_newer should contain both values
934 assert_eq!(cx_newer.get::<ValueA>(), Some(&ValueA(1)));
935 assert_eq!(cx_newer.get::<ValueB>(), Some(&ValueB(1)));
936 }
937
938 #[test]
939 fn nested_contexts() {
940 let _outer_guard = Context::new().with_value(ValueA(1)).attach();
941
942 // Only value `a` is set
943 let current = Context::current();
944 assert_eq!(current.get(), Some(&ValueA(1)));
945 assert_eq!(current.get::<ValueB>(), None);
946
947 {
948 let _inner_guard = Context::current_with_value(ValueB(42)).attach();
949 // Both values are set in inner context
950 let current = Context::current();
951 assert_eq!(current.get(), Some(&ValueA(1)));
952 assert_eq!(current.get(), Some(&ValueB(42)));
953
954 assert!(Context::map_current(|cx| {
955 assert_eq!(cx.get(), Some(&ValueA(1)));
956 assert_eq!(cx.get(), Some(&ValueB(42)));
957 true
958 }));
959 }
960
961 // Resets to only value `a` when inner guard is dropped
962 let current = Context::current();
963 assert_eq!(current.get(), Some(&ValueA(1)));
964 assert_eq!(current.get::<ValueB>(), None);
965
966 assert!(Context::map_current(|cx| {
967 assert_eq!(cx.get(), Some(&ValueA(1)));
968 assert_eq!(cx.get::<ValueB>(), None);
969 true
970 }));
971 }
972
973 #[test]
974 fn overlapping_contexts() {
975 let outer_guard = Context::new().with_value(ValueA(1)).attach();
976
977 // Only value `a` is set
978 let current = Context::current();
979 assert_eq!(current.get(), Some(&ValueA(1)));
980 assert_eq!(current.get::<ValueB>(), None);
981
982 let inner_guard = Context::current_with_value(ValueB(42)).attach();
983 // Both values are set in inner context
984 let current = Context::current();
985 assert_eq!(current.get(), Some(&ValueA(1)));
986 assert_eq!(current.get(), Some(&ValueB(42)));
987
988 assert!(Context::map_current(|cx| {
989 assert_eq!(cx.get(), Some(&ValueA(1)));
990 assert_eq!(cx.get(), Some(&ValueB(42)));
991 true
992 }));
993
994 drop(outer_guard);
995
996 // `inner_guard` is still alive so both `ValueA` and `ValueB` should still be accessible
997 let current = Context::current();
998 assert_eq!(current.get(), Some(&ValueA(1)));
999 assert_eq!(current.get(), Some(&ValueB(42)));
1000
1001 drop(inner_guard);
1002
1003 // Both guards are dropped and neither value should be accessible.
1004 let current = Context::current();
1005 assert_eq!(current.get::<ValueA>(), None);
1006 assert_eq!(current.get::<ValueB>(), None);
1007 }
1008
1009 #[test]
1010 fn too_many_contexts() {
1011 let mut guards: Vec<ContextGuard> = Vec::with_capacity(ContextStack::MAX_POS as usize);
1012 let stack_max_pos = ContextStack::MAX_POS as u64;
1013 // Fill the stack up until the last position
1014 for i in 1..stack_max_pos {
1015 let cx_guard = Context::current().with_value(ValueB(i)).attach();
1016 assert_eq!(Context::current().get(), Some(&ValueB(i)));
1017 assert_eq!(cx_guard.cx_pos, i as u16);
1018 guards.push(cx_guard);
1019 }
1020 // Let's overflow the stack a couple of times
1021 for _ in 0..16 {
1022 let cx_guard = Context::current().with_value(ValueA(1)).attach();
1023 assert_eq!(cx_guard.cx_pos, ContextStack::MAX_POS);
1024 assert_eq!(Context::current().get::<ValueA>(), None);
1025 assert_eq!(Context::current().get(), Some(&ValueB(stack_max_pos - 1)));
1026 guards.push(cx_guard);
1027 }
1028 // Drop the overflow contexts
1029 for _ in 0..16 {
1030 guards.pop();
1031 assert_eq!(Context::current().get::<ValueA>(), None);
1032 assert_eq!(Context::current().get(), Some(&ValueB(stack_max_pos - 1)));
1033 }
1034 // Drop one more so we can add a new one
1035 guards.pop();
1036 assert_eq!(Context::current().get::<ValueA>(), None);
1037 assert_eq!(Context::current().get(), Some(&ValueB(stack_max_pos - 2)));
1038 // Push a new context and see that it works
1039 let cx_guard = Context::current().with_value(ValueA(2)).attach();
1040 assert_eq!(cx_guard.cx_pos, ContextStack::MAX_POS - 1);
1041 assert_eq!(Context::current().get(), Some(&ValueA(2)));
1042 assert_eq!(Context::current().get(), Some(&ValueB(stack_max_pos - 2)));
1043 guards.push(cx_guard);
1044 // Let's overflow the stack a couple of times again
1045 for _ in 0..16 {
1046 let cx_guard = Context::current().with_value(ValueA(1)).attach();
1047 assert_eq!(cx_guard.cx_pos, ContextStack::MAX_POS);
1048 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(2)));
1049 assert_eq!(Context::current().get(), Some(&ValueB(stack_max_pos - 2)));
1050 guards.push(cx_guard);
1051 }
1052 }
1053
1054 /// Tests that a new ContextStack is created with the correct initial capacity.
1055 #[test]
1056 fn test_initial_capacity() {
1057 let stack = ContextStack::default();
1058 assert_eq!(stack.stack.capacity(), ContextStack::INITIAL_CAPACITY);
1059 }
1060
1061 /// Tests that map_current_cx correctly accesses the current context.
1062 #[test]
1063 fn test_map_current_cx() {
1064 let mut stack = ContextStack::default();
1065 let test_value = ValueA(42);
1066 stack.current_cx = Context::new().with_value(test_value);
1067
1068 let result = stack.map_current_cx(|cx| {
1069 assert_eq!(cx.get::<ValueA>(), Some(&ValueA(42)));
1070 true
1071 });
1072 assert!(result);
1073 }
1074
1075 /// Tests popping contexts in non-sequential order.
1076 #[test]
1077 fn test_pop_id_out_of_order() {
1078 let mut stack = ContextStack::default();
1079
1080 // Push three contexts
1081 let cx1 = Context::new().with_value(ValueA(1));
1082 let cx2 = Context::new().with_value(ValueA(2));
1083 let cx3 = Context::new().with_value(ValueA(3));
1084
1085 let id1 = stack.push(cx1);
1086 let id2 = stack.push(cx2);
1087 let id3 = stack.push(cx3);
1088
1089 // Pop middle context first - should not affect current context
1090 stack.pop_id(id2);
1091 assert_eq!(stack.current_cx.get::<ValueA>(), Some(&ValueA(3)));
1092 assert_eq!(stack.stack.len(), 3); // Length unchanged for middle pops
1093
1094 // Pop last context - should restore previous valid context
1095 stack.pop_id(id3);
1096 assert_eq!(stack.current_cx.get::<ValueA>(), Some(&ValueA(1)));
1097 assert_eq!(stack.stack.len(), 1);
1098
1099 // Pop first context - should restore to empty state
1100 stack.pop_id(id1);
1101 assert_eq!(stack.current_cx.get::<ValueA>(), None);
1102 assert_eq!(stack.stack.len(), 0);
1103 }
1104
1105 /// Tests edge cases in context stack operations. IRL these should log
1106 /// warnings, and definitely not panic.
1107 #[test]
1108 fn test_pop_id_edge_cases() {
1109 let mut stack = ContextStack::default();
1110
1111 // Test popping BASE_POS - should be no-op
1112 stack.pop_id(ContextStack::BASE_POS);
1113 assert_eq!(stack.stack.len(), 0);
1114
1115 // Test popping MAX_POS - should be no-op
1116 stack.pop_id(ContextStack::MAX_POS);
1117 assert_eq!(stack.stack.len(), 0);
1118
1119 // Test popping invalid position - should be no-op
1120 stack.pop_id(1000);
1121 assert_eq!(stack.stack.len(), 0);
1122
1123 // Test popping from empty stack - should be safe
1124 stack.pop_id(1);
1125 assert_eq!(stack.stack.len(), 0);
1126 }
1127
1128 /// Tests stack behavior when reaching maximum capacity.
1129 /// Once we push beyond this point, we should end up with a context
1130 /// that points _somewhere_, but mutating it should not affect the current
1131 /// active context.
1132 #[test]
1133 fn test_push_overflow() {
1134 let mut stack = ContextStack::default();
1135 let max_pos = ContextStack::MAX_POS as usize;
1136
1137 // Fill stack up to max position
1138 for i in 0..max_pos {
1139 let cx = Context::new().with_value(ValueA(i as u64));
1140 let id = stack.push(cx);
1141 assert_eq!(id, (i + 1) as u16);
1142 }
1143
1144 // Try to push beyond capacity
1145 let cx = Context::new().with_value(ValueA(max_pos as u64));
1146 let id = stack.push(cx);
1147 assert_eq!(id, ContextStack::MAX_POS);
1148
1149 // Verify current context remains unchanged after overflow
1150 assert_eq!(
1151 stack.current_cx.get::<ValueA>(),
1152 Some(&ValueA((max_pos - 2) as u64))
1153 );
1154 }
1155
1156 /// Tests that:
1157 /// 1. Parent context values are properly propagated to async operations
1158 /// 2. Values added during async operations do not affect parent context
1159 #[tokio::test]
1160 async fn test_async_context_propagation() {
1161 // A nested async operation we'll use to test propagation
1162 async fn nested_operation() {
1163 // Verify we can see the parent context's value
1164 assert_eq!(
1165 Context::current().get::<ValueA>(),
1166 Some(&ValueA(42)),
1167 "Parent context value should be available in async operation"
1168 );
1169
1170 // Create new context
1171 let cx_with_both = Context::current()
1172 .with_value(ValueA(43)) // override ValueA
1173 .with_value(ValueB(24)); // Add new ValueB
1174
1175 // Run nested async operation with both values
1176 async {
1177 // Verify both values are available
1178 assert_eq!(
1179 Context::current().get::<ValueA>(),
1180 Some(&ValueA(43)),
1181 "Parent value should still be available after adding new value"
1182 );
1183 assert_eq!(
1184 Context::current().get::<ValueB>(),
1185 Some(&ValueB(24)),
1186 "New value should be available in async operation"
1187 );
1188
1189 // Do some async work to simulate real-world scenario
1190 sleep(Duration::from_millis(10)).await;
1191
1192 // Values should still be available after async work
1193 assert_eq!(
1194 Context::current().get::<ValueA>(),
1195 Some(&ValueA(43)),
1196 "Parent value should persist across await points"
1197 );
1198 assert_eq!(
1199 Context::current().get::<ValueB>(),
1200 Some(&ValueB(24)),
1201 "New value should persist across await points"
1202 );
1203 }
1204 .with_context(cx_with_both)
1205 .await;
1206 }
1207
1208 // Set up initial context with ValueA
1209 let parent_cx = Context::new().with_value(ValueA(42));
1210
1211 // Create and run async operation with the parent context explicitly propagated
1212 nested_operation().with_context(parent_cx.clone()).await;
1213
1214 // After async operation completes:
1215 // 1. Parent context should be unchanged
1216 assert_eq!(
1217 parent_cx.get::<ValueA>(),
1218 Some(&ValueA(42)),
1219 "Parent context should be unchanged"
1220 );
1221 assert_eq!(
1222 parent_cx.get::<ValueB>(),
1223 None,
1224 "Parent context should not see values added in async operation"
1225 );
1226
1227 // 2. Current context should be back to default
1228 assert_eq!(
1229 Context::current().get::<ValueA>(),
1230 None,
1231 "Current context should be back to default"
1232 );
1233 assert_eq!(
1234 Context::current().get::<ValueB>(),
1235 None,
1236 "Current context should not have async operation's values"
1237 );
1238 }
1239
1240 ///
1241 /// Tests that unnatural parent->child relationships in nested async
1242 /// operations behave properly.
1243 ///
1244 #[tokio::test]
1245 async fn test_out_of_order_context_detachment_futures() {
1246 // This function returns a future, but doesn't await it
1247 // It will complete before the future that it creates.
1248 async fn create_a_future() -> impl std::future::Future<Output = ()> {
1249 // Create a future that will do some work, referencing our current
1250 // context, but don't await it.
1251 async {
1252 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(42)));
1253
1254 // Longer work
1255 sleep(Duration::from_millis(50)).await;
1256 }
1257 .with_context(Context::current())
1258 }
1259
1260 // Create our base context
1261 let parent_cx = Context::new().with_value(ValueA(42));
1262
1263 // await our nested function, which will create and detach a context
1264 let future = create_a_future().with_context(parent_cx).await;
1265
1266 // Execute the future. The future that created it is long gone, but this shouldn't
1267 // cause issues.
1268 let _a = future.await;
1269
1270 // Nothing terrible (e.g., panics!) should happen, and we should definitely not have any
1271 // values attached to our current context that were set in the nested operations.
1272 assert_eq!(Context::current().get::<ValueA>(), None);
1273 assert_eq!(Context::current().get::<ValueB>(), None);
1274 }
1275
1276 #[test]
1277 fn test_is_telemetry_suppressed() {
1278 // Default context has suppression disabled
1279 let cx = Context::new();
1280 assert!(!cx.is_telemetry_suppressed());
1281
1282 // With suppression enabled
1283 let suppressed = cx.with_telemetry_suppressed();
1284 assert!(suppressed.is_telemetry_suppressed());
1285 }
1286
1287 #[test]
1288 fn test_with_telemetry_suppressed() {
1289 // Start with a normal context
1290 let cx = Context::new();
1291 assert!(!cx.is_telemetry_suppressed());
1292
1293 // Create a suppressed context
1294 let suppressed = cx.with_telemetry_suppressed();
1295
1296 // Original should remain unchanged
1297 assert!(!cx.is_telemetry_suppressed());
1298
1299 // New context should be suppressed
1300 assert!(suppressed.is_telemetry_suppressed());
1301
1302 // Test with values to ensure they're preserved
1303 let cx_with_value = cx.with_value(ValueA(42));
1304 let suppressed_with_value = cx_with_value.with_telemetry_suppressed();
1305
1306 assert!(!cx_with_value.is_telemetry_suppressed());
1307 assert!(suppressed_with_value.is_telemetry_suppressed());
1308 assert_eq!(suppressed_with_value.get::<ValueA>(), Some(&ValueA(42)));
1309 }
1310
1311 #[test]
1312 fn test_enter_telemetry_suppressed_scope() {
1313 // Ensure we start with a clean context
1314 let _reset_guard = Context::new().attach();
1315
1316 // Default context should not be suppressed
1317 assert!(!Context::is_current_telemetry_suppressed());
1318
1319 // Add an entry to the current context
1320 let cx_with_value = Context::current().with_value(ValueA(42));
1321 let _guard_with_value = cx_with_value.attach();
1322
1323 // Verify the entry is present and context is not suppressed
1324 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(42)));
1325 assert!(!Context::is_current_telemetry_suppressed());
1326
1327 // Enter a suppressed scope
1328 {
1329 let _guard = Context::enter_telemetry_suppressed_scope();
1330
1331 // Verify suppression is active and the entry is still present
1332 assert!(Context::is_current_telemetry_suppressed());
1333 assert!(Context::current().is_telemetry_suppressed());
1334 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(42)));
1335 }
1336
1337 // After guard is dropped, should be back to unsuppressed and entry should still be present
1338 assert!(!Context::is_current_telemetry_suppressed());
1339 assert!(!Context::current().is_telemetry_suppressed());
1340 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(42)));
1341 }
1342
1343 #[test]
1344 fn test_nested_suppression_scopes() {
1345 // Ensure we start with a clean context
1346 let _reset_guard = Context::new().attach();
1347
1348 // Default context should not be suppressed
1349 assert!(!Context::is_current_telemetry_suppressed());
1350
1351 // First level suppression
1352 {
1353 let _outer = Context::enter_telemetry_suppressed_scope();
1354 assert!(Context::is_current_telemetry_suppressed());
1355
1356 // Second level. This component is unaware of Suppression,
1357 // and just attaches a new context. Since it is from current,
1358 // it'll already have suppression enabled.
1359 {
1360 let _inner = Context::current().with_value(ValueA(1)).attach();
1361 assert!(Context::is_current_telemetry_suppressed());
1362 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(1)));
1363 }
1364
1365 // Another scenario. This component is unaware of Suppression,
1366 // and just attaches a new context, not from Current. Since it is
1367 // not from current it will not have suppression enabled.
1368 {
1369 let _inner = Context::new().with_value(ValueA(1)).attach();
1370 assert!(!Context::is_current_telemetry_suppressed());
1371 assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA(1)));
1372 }
1373
1374 // Still suppressed after inner scope
1375 assert!(Context::is_current_telemetry_suppressed());
1376 }
1377
1378 // Back to unsuppressed
1379 assert!(!Context::is_current_telemetry_suppressed());
1380 }
1381
1382 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1383 async fn test_async_suppression() {
1384 async fn nested_operation() {
1385 assert!(Context::is_current_telemetry_suppressed());
1386
1387 let cx_with_additional_value = Context::current().with_value(ValueB(24));
1388
1389 async {
1390 assert_eq!(
1391 Context::current().get::<ValueB>(),
1392 Some(&ValueB(24)),
1393 "Parent value should still be available after adding new value"
1394 );
1395 assert!(Context::is_current_telemetry_suppressed());
1396
1397 // Do some async work to simulate real-world scenario
1398 sleep(Duration::from_millis(10)).await;
1399
1400 // Values should still be available after async work
1401 assert_eq!(
1402 Context::current().get::<ValueB>(),
1403 Some(&ValueB(24)),
1404 "Parent value should still be available after adding new value"
1405 );
1406 assert!(Context::is_current_telemetry_suppressed());
1407 }
1408 .with_context(cx_with_additional_value)
1409 .await;
1410 }
1411
1412 // Set up suppressed context, but don't attach it to current
1413 let suppressed_parent = Context::new().with_telemetry_suppressed();
1414 // Current should not be suppressed as we haven't attached it
1415 assert!(!Context::is_current_telemetry_suppressed());
1416
1417 // Create and run async operation with the suppressed context explicitly propagated
1418 nested_operation()
1419 .with_context(suppressed_parent.clone())
1420 .await;
1421
1422 // After async operation completes:
1423 // Suppression should be active
1424 assert!(suppressed_parent.is_telemetry_suppressed());
1425
1426 // Current should still be not suppressed
1427 assert!(!Context::is_current_telemetry_suppressed());
1428 }
1429}
1430
1431#[cfg(all(test, feature = "experimental_context_observer"))]
1432mod observer_tests {
1433 use super::*;
1434 use std::cell::{Cell, RefCell};
1435 use std::sync::Arc;
1436
1437 #[derive(Debug, PartialEq)]
1438 struct V(u64);
1439
1440 #[derive(Debug, PartialEq, Clone)]
1441 enum Event {
1442 Enter { from: Option<u64>, to: Option<u64> },
1443 Exit { from: Option<u64>, to: Option<u64> },
1444 }
1445
1446 thread_local! {
1447 static EVENTS: RefCell<Vec<Event>> = const { RefCell::new(Vec::new()) };
1448 }
1449
1450 fn value_of(cx: &Context) -> Option<u64> {
1451 cx.get::<V>().map(|v| v.0)
1452 }
1453
1454 // When enabled, on each transition it records the event into a thread-local buffer, and on
1455 // enter it also installs an observer view on the entered context and bumps the thread-local
1456 // `VIEWS_CREATED` counter. See `observer_installs_and_reuses_view`.
1457 //
1458 // The observer is a process-global singleton, so its callbacks fire for context activity on
1459 // *every* thread across the whole test binary once installed. To keep that cheap and isolated,
1460 // all of its work is gated behind the per-thread `ENABLE_TEST_OBSERVER` switch: threads that
1461 // didn't opt in (via `setup()`) get an inert observer.
1462 struct RecordingObserver;
1463
1464 impl ContextObserver for RecordingObserver {
1465 fn on_context_enter(&self, from: &Context, to: &Context) {
1466 if !ENABLE_TEST_OBSERVER.with(Cell::get) {
1467 return;
1468 }
1469
1470 EVENTS.with(|e| {
1471 e.borrow_mut().push(Event::Enter {
1472 from: value_of(from),
1473 to: value_of(to),
1474 })
1475 });
1476 }
1477
1478 fn on_context_exit(&self, from: &Context, to: &Context) {
1479 if !ENABLE_TEST_OBSERVER.with(Cell::get) {
1480 return;
1481 }
1482
1483 EVENTS.with(|e| {
1484 e.borrow_mut().push(Event::Exit {
1485 from: value_of(from),
1486 to: value_of(to),
1487 })
1488 });
1489 }
1490
1491 // On context enter, install the observer's view if this context doesn't have one yet, or reuse
1492 // the existing one otherwise. The view is the context's `V` value (or 0 for a value-less
1493 // context).
1494 fn make_view(&self, cx: &Context) -> Option<Arc<dyn ObserverContextView>> {
1495 let correlation_id = value_of(cx).unwrap_or(0);
1496 VIEWS_CREATED.with(|c| c.set(c.get() + 1));
1497 Some(Arc::new(MyView { correlation_id }))
1498 }
1499 }
1500
1501 thread_local! {
1502 // Master switch for the observer per thread. Enabled for the duration of a test that cares,
1503 // and left false everywhere else, so we don't slow down tests unrelated to the observer.
1504 // See `setup`.
1505 static ENABLE_TEST_OBSERVER: Cell<bool> = const { Cell::new(false) };
1506 // Counts how many distinct views the observer created on the current thread.
1507 static VIEWS_CREATED: Cell<u64> = const { Cell::new(0) };
1508 }
1509
1510 // Recovers the correlation id stored in a context's observer view, if any.
1511 fn view_id(cx: &Context) -> Option<u64> {
1512 cx.observer_view()
1513 .as_ref()
1514 .and_then(|v| v.as_any().downcast_ref::<MyView>())
1515 .map(|v| v.correlation_id)
1516 }
1517
1518 // Holds the base context guard and disables the observer on this thread when dropped. The
1519 // struct's own `Drop` runs before its fields, so the flag is cleared before the base context is
1520 // popped, which is thus not observed (symmetrically to `setup`, which installs the base context
1521 // before registering/enabling the observer).
1522 struct ObserverTestGuard {
1523 _cx: ContextGuard,
1524 }
1525
1526 impl Drop for ObserverTestGuard {
1527 fn drop(&mut self) {
1528 ENABLE_TEST_OBSERVER.with(|e| e.set(false));
1529 }
1530 }
1531
1532 // Enables the observer on this thread, establishes a clean base context, and clears any
1533 // previously recorded events, so the assertions below only see transitions this test
1534 // triggers. Dropping the returned guard disables the observer again.
1535 fn setup_observer() -> ObserverTestGuard {
1536 // Idempotent across tests: only the first call installs the observer, but every test
1537 // uses the same `RecordingObserver`, so a lost race is harmless.
1538 GlobalContextObserver::set(Arc::new(RecordingObserver));
1539 ENABLE_TEST_OBSERVER.with(|e| e.set(true));
1540 let guard = Context::new().attach();
1541 EVENTS.with(|e| e.borrow_mut().clear());
1542 ObserverTestGuard { _cx: guard }
1543 }
1544
1545 #[test]
1546 fn global_observer_records_enter_and_exit() {
1547 let _clean = setup_observer();
1548
1549 {
1550 let _g = Context::current().with_value(V(1)).attach();
1551 // Entering fires immediately: from the clean base (no value) to `V(1)`.
1552 EVENTS.with(|e| {
1553 assert_eq!(
1554 *e.borrow(),
1555 vec![Event::Enter {
1556 from: None,
1557 to: Some(1)
1558 }]
1559 )
1560 });
1561 }
1562
1563 // Dropping the (context) guard restores the base and fires the matching exit event.
1564 EVENTS.with(|e| {
1565 assert_eq!(
1566 *e.borrow(),
1567 vec![
1568 Event::Enter {
1569 from: None,
1570 to: Some(1)
1571 },
1572 Event::Exit {
1573 from: Some(1),
1574 to: None
1575 }
1576 ]
1577 )
1578 });
1579 }
1580
1581 #[test]
1582 fn global_observer_records_nested_transitions() {
1583 let _clean = setup_observer();
1584
1585 let g1 = Context::current().with_value(V(1)).attach();
1586 let g2 = Context::current().with_value(V(2)).attach();
1587 drop(g2);
1588 drop(g1);
1589
1590 EVENTS.with(|e| {
1591 assert_eq!(
1592 *e.borrow(),
1593 vec![
1594 Event::Enter {
1595 from: None,
1596 to: Some(1)
1597 },
1598 Event::Enter {
1599 from: Some(1),
1600 to: Some(2)
1601 },
1602 Event::Exit {
1603 from: Some(2),
1604 to: Some(1)
1605 },
1606 Event::Exit {
1607 from: Some(1),
1608 to: None
1609 },
1610 ]
1611 )
1612 });
1613 }
1614
1615 // An observer view carrying arbitrary data, unrelated to the internal `Context`
1616 // representation.
1617 struct MyView {
1618 correlation_id: u64,
1619 }
1620
1621 impl ObserverContextView for MyView {
1622 fn as_any(&self) -> &dyn Any {
1623 self
1624 }
1625 }
1626
1627 // A realistic use of the observer view: the global observer installs its own view on each
1628 // context the first time that context is entered, and reuses it on re-entry. The
1629 // thread-local counter checks that exactly one view is created per distinct context.
1630 #[test]
1631 fn observer_installs_and_reuses_view() {
1632 let _clean = setup_observer();
1633
1634 // Reset the counter for the work below.
1635 VIEWS_CREATED.with(|c| c.set(0));
1636
1637 // Entering two distinct contexts installs a fresh view for each, derived from its value.
1638 let g1 = Context::current().with_value(V(1)).attach();
1639 assert_eq!(view_id(&Context::current()), Some(1));
1640
1641 let g2 = Context::current().with_value(V(2)).attach();
1642 assert_eq!(view_id(&Context::current()), Some(2));
1643
1644 assert_eq!(VIEWS_CREATED.with(Cell::get), 2);
1645
1646 // Re-entering an already-observed context must reuse its view, not create a new one.
1647 // `Context::current()` clones the current context, carrying along its already-set view.
1648 let observed = Context::current();
1649 let g3 = observed.attach();
1650
1651 assert_eq!(view_id(&Context::current()), Some(2));
1652 assert_eq!(
1653 VIEWS_CREATED.with(Cell::get),
1654 2,
1655 "re-entering an observed context must not create a new view"
1656 );
1657
1658 drop(g3);
1659 drop(g2);
1660 drop(g1);
1661 }
1662}