1 | //! Storage for span data shared by multiple [`Layer`]s. |
2 | //! |
3 | //! ## Using the Span Registry |
4 | //! |
5 | //! This module provides the [`Registry`] type, a [`Subscriber`] implementation |
6 | //! which tracks per-span data and exposes it to [`Layer`]s. When a `Registry` |
7 | //! is used as the base `Subscriber` of a `Layer` stack, the |
8 | //! [`layer::Context`][ctx] type will provide methods allowing `Layer`s to |
9 | //! [look up span data][lookup] stored in the registry. While [`Registry`] is a |
10 | //! reasonable default for storing spans and events, other stores that implement |
11 | //! [`LookupSpan`] and [`Subscriber`] themselves (with [`SpanData`] implemented |
12 | //! by the per-span data they store) can be used as a drop-in replacement. |
13 | //! |
14 | //! For example, we might create a `Registry` and add multiple `Layer`s like so: |
15 | //! ```rust |
16 | //! use tracing_subscriber::{registry::Registry, Layer, prelude::*}; |
17 | //! # use tracing_core::Subscriber; |
18 | //! # pub struct FooLayer {} |
19 | //! # pub struct BarLayer {} |
20 | //! # impl<S: Subscriber> Layer<S> for FooLayer {} |
21 | //! # impl<S: Subscriber> Layer<S> for BarLayer {} |
22 | //! # impl FooLayer { |
23 | //! # fn new() -> Self { Self {} } |
24 | //! # } |
25 | //! # impl BarLayer { |
26 | //! # fn new() -> Self { Self {} } |
27 | //! # } |
28 | //! |
29 | //! let subscriber = Registry::default() |
30 | //! .with(FooLayer::new()) |
31 | //! .with(BarLayer::new()); |
32 | //! ``` |
33 | //! |
34 | //! If a type implementing `Layer` depends on the functionality of a `Registry` |
35 | //! implementation, it should bound its `Subscriber` type parameter with the |
36 | //! [`LookupSpan`] trait, like so: |
37 | //! |
38 | //! ```rust |
39 | //! use tracing_subscriber::{registry, Layer}; |
40 | //! use tracing_core::Subscriber; |
41 | //! |
42 | //! pub struct MyLayer { |
43 | //! // ... |
44 | //! } |
45 | //! |
46 | //! impl<S> Layer<S> for MyLayer |
47 | //! where |
48 | //! S: Subscriber + for<'a> registry::LookupSpan<'a>, |
49 | //! { |
50 | //! // ... |
51 | //! } |
52 | //! ``` |
53 | //! When this bound is added, the `Layer` implementation will be guaranteed |
54 | //! access to the [`Context`][ctx] methods, such as [`Context::span`][lookup], that |
55 | //! require the root subscriber to be a registry. |
56 | //! |
57 | //! [`Layer`]: crate::layer::Layer |
58 | //! [`Subscriber`]: tracing_core::Subscriber |
59 | //! [ctx]: crate::layer::Context |
60 | //! [lookup]: crate::layer::Context::span() |
61 | use tracing_core::{field::FieldSet, span::Id, Metadata}; |
62 | |
63 | feature! { |
64 | #![feature = "std" ] |
65 | /// A module containing a type map of span extensions. |
66 | mod extensions; |
67 | pub use extensions::{Extensions, ExtensionsMut}; |
68 | |
69 | } |
70 | |
71 | feature! { |
72 | #![all(feature = "registry" , feature = "std" )] |
73 | |
74 | mod sharded; |
75 | mod stack; |
76 | |
77 | pub use sharded::Data; |
78 | pub use sharded::Registry; |
79 | |
80 | use crate::filter::FilterId; |
81 | } |
82 | |
83 | /// Provides access to stored span data. |
84 | /// |
85 | /// Subscribers which store span data and associate it with span IDs should |
86 | /// implement this trait; if they do, any [`Layer`]s wrapping them can look up |
87 | /// metadata via the [`Context`] type's [`span()`] method. |
88 | /// |
89 | /// [`Layer`]: super::layer::Layer |
90 | /// [`Context`]: super::layer::Context |
91 | /// [`span()`]: super::layer::Context::span |
92 | pub trait LookupSpan<'a> { |
93 | /// The type of span data stored in this registry. |
94 | type Data: SpanData<'a>; |
95 | |
96 | /// Returns the [`SpanData`] for a given `Id`, if it exists. |
97 | /// |
98 | /// <pre class="ignore" style="white-space:normal;font:inherit;"> |
99 | /// <strong>Note</strong>: users of the <code>LookupSpan</code> trait should |
100 | /// typically call the <a href="#method.span"><code>span</code></a> method rather |
101 | /// than this method. The <code>span</code> method is implemented by |
102 | /// <em>calling</em> <code>span_data</code>, but returns a reference which is |
103 | /// capable of performing more sophisiticated queries. |
104 | /// </pre> |
105 | /// |
106 | fn span_data(&'a self, id: &Id) -> Option<Self::Data>; |
107 | |
108 | /// Returns a [`SpanRef`] for the span with the given `Id`, if it exists. |
109 | /// |
110 | /// A `SpanRef` is similar to [`SpanData`], but it allows performing |
111 | /// additional lookups against the registryr that stores the wrapped data. |
112 | /// |
113 | /// In general, _users_ of the `LookupSpan` trait should use this method |
114 | /// rather than the [`span_data`] method; while _implementors_ of this trait |
115 | /// should only implement `span_data`. |
116 | /// |
117 | /// [`span_data`]: LookupSpan::span_data() |
118 | fn span(&'a self, id: &Id) -> Option<SpanRef<'_, Self>> |
119 | where |
120 | Self: Sized, |
121 | { |
122 | let data = self.span_data(id)?; |
123 | Some(SpanRef { |
124 | registry: self, |
125 | data, |
126 | #[cfg (feature = "registry" )] |
127 | filter: FilterId::none(), |
128 | }) |
129 | } |
130 | |
131 | /// Registers a [`Filter`] for [per-layer filtering] with this |
132 | /// [`Subscriber`]. |
133 | /// |
134 | /// The [`Filter`] can then use the returned [`FilterId`] to |
135 | /// [check if it previously enabled a span][check]. |
136 | /// |
137 | /// # Panics |
138 | /// |
139 | /// If this `Subscriber` does not support [per-layer filtering]. |
140 | /// |
141 | /// [`Filter`]: crate::layer::Filter |
142 | /// [per-layer filtering]: crate::layer::Layer#per-layer-filtering |
143 | /// [`Subscriber`]: tracing_core::Subscriber |
144 | /// [`FilterId`]: crate::filter::FilterId |
145 | /// [check]: SpanData::is_enabled_for |
146 | #[cfg (feature = "registry" )] |
147 | #[cfg_attr (docsrs, doc(cfg(feature = "registry" )))] |
148 | fn register_filter(&mut self) -> FilterId { |
149 | panic!( |
150 | "{} does not currently support filters" , |
151 | std::any::type_name::<Self>() |
152 | ) |
153 | } |
154 | } |
155 | |
156 | /// A stored representation of data associated with a span. |
157 | pub trait SpanData<'a> { |
158 | /// Returns this span's ID. |
159 | fn id(&self) -> Id; |
160 | |
161 | /// Returns a reference to the span's `Metadata`. |
162 | fn metadata(&self) -> &'static Metadata<'static>; |
163 | |
164 | /// Returns a reference to the ID |
165 | fn parent(&self) -> Option<&Id>; |
166 | |
167 | /// Returns a reference to this span's `Extensions`. |
168 | /// |
169 | /// The extensions may be used by `Layer`s to store additional data |
170 | /// describing the span. |
171 | #[cfg (feature = "std" )] |
172 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
173 | fn extensions(&self) -> Extensions<'_>; |
174 | |
175 | /// Returns a mutable reference to this span's `Extensions`. |
176 | /// |
177 | /// The extensions may be used by `Layer`s to store additional data |
178 | /// describing the span. |
179 | #[cfg (feature = "std" )] |
180 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
181 | fn extensions_mut(&self) -> ExtensionsMut<'_>; |
182 | |
183 | /// Returns `true` if this span is enabled for the [per-layer filter][plf] |
184 | /// corresponding to the provided [`FilterId`]. |
185 | /// |
186 | /// ## Default Implementation |
187 | /// |
188 | /// By default, this method assumes that the [`LookupSpan`] implementation |
189 | /// does not support [per-layer filtering][plf], and always returns `true`. |
190 | /// |
191 | /// [plf]: crate::layer::Layer#per-layer-filtering |
192 | /// [`FilterId`]: crate::filter::FilterId |
193 | #[cfg (feature = "registry" )] |
194 | #[cfg_attr (docsrs, doc(cfg(feature = "registry" )))] |
195 | fn is_enabled_for(&self, filter: FilterId) -> bool { |
196 | let _ = filter; |
197 | true |
198 | } |
199 | } |
200 | |
201 | /// A reference to [span data] and the associated [registry]. |
202 | /// |
203 | /// This type implements all the same methods as [`SpanData`], and provides |
204 | /// additional methods for querying the registry based on values from the span. |
205 | /// |
206 | /// [registry]: LookupSpan |
207 | #[derive(Debug)] |
208 | pub struct SpanRef<'a, R: LookupSpan<'a>> { |
209 | registry: &'a R, |
210 | data: R::Data, |
211 | |
212 | #[cfg (feature = "registry" )] |
213 | filter: FilterId, |
214 | } |
215 | |
216 | /// An iterator over the parents of a span, ordered from leaf to root. |
217 | /// |
218 | /// This is returned by the [`SpanRef::scope`] method. |
219 | #[derive(Debug)] |
220 | pub struct Scope<'a, R> { |
221 | registry: &'a R, |
222 | next: Option<Id>, |
223 | |
224 | #[cfg (all(feature = "registry" , feature = "std" ))] |
225 | filter: FilterId, |
226 | } |
227 | |
228 | feature! { |
229 | #![any(feature = "alloc" , feature = "std" )] |
230 | |
231 | #[cfg (not(feature = "smallvec" ))] |
232 | use alloc::vec::{self, Vec}; |
233 | |
234 | use core::{fmt,iter}; |
235 | |
236 | /// An iterator over the parents of a span, ordered from root to leaf. |
237 | /// |
238 | /// This is returned by the [`Scope::from_root`] method. |
239 | pub struct ScopeFromRoot<'a, R> |
240 | where |
241 | R: LookupSpan<'a>, |
242 | { |
243 | #[cfg (feature = "smallvec" )] |
244 | spans: iter::Rev<smallvec::IntoIter<SpanRefVecArray<'a, R>>>, |
245 | #[cfg (not(feature = "smallvec" ))] |
246 | spans: iter::Rev<vec::IntoIter<SpanRef<'a, R>>>, |
247 | } |
248 | |
249 | #[cfg (feature = "smallvec" )] |
250 | type SpanRefVecArray<'span, L> = [SpanRef<'span, L>; 16]; |
251 | |
252 | impl<'a, R> Scope<'a, R> |
253 | where |
254 | R: LookupSpan<'a>, |
255 | { |
256 | /// Flips the order of the iterator, so that it is ordered from root to leaf. |
257 | /// |
258 | /// The iterator will first return the root span, then that span's immediate child, |
259 | /// and so on until it finally returns the span that [`SpanRef::scope`] was called on. |
260 | /// |
261 | /// If any items were consumed from the [`Scope`] before calling this method then they |
262 | /// will *not* be returned from the [`ScopeFromRoot`]. |
263 | /// |
264 | /// **Note**: this will allocate if there are many spans remaining, or if the |
265 | /// "smallvec" feature flag is not enabled. |
266 | #[allow (clippy::wrong_self_convention)] |
267 | pub fn from_root(self) -> ScopeFromRoot<'a, R> { |
268 | #[cfg (feature = "smallvec" )] |
269 | type Buf<T> = smallvec::SmallVec<T>; |
270 | #[cfg (not(feature = "smallvec" ))] |
271 | type Buf<T> = Vec<T>; |
272 | ScopeFromRoot { |
273 | spans: self.collect::<Buf<_>>().into_iter().rev(), |
274 | } |
275 | } |
276 | } |
277 | |
278 | impl<'a, R> Iterator for ScopeFromRoot<'a, R> |
279 | where |
280 | R: LookupSpan<'a>, |
281 | { |
282 | type Item = SpanRef<'a, R>; |
283 | |
284 | #[inline ] |
285 | fn next(&mut self) -> Option<Self::Item> { |
286 | self.spans.next() |
287 | } |
288 | |
289 | #[inline ] |
290 | fn size_hint(&self) -> (usize, Option<usize>) { |
291 | self.spans.size_hint() |
292 | } |
293 | } |
294 | |
295 | impl<'a, R> fmt::Debug for ScopeFromRoot<'a, R> |
296 | where |
297 | R: LookupSpan<'a>, |
298 | { |
299 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
300 | f.pad("ScopeFromRoot { .. }" ) |
301 | } |
302 | } |
303 | } |
304 | |
305 | impl<'a, R> Iterator for Scope<'a, R> |
306 | where |
307 | R: LookupSpan<'a>, |
308 | { |
309 | type Item = SpanRef<'a, R>; |
310 | |
311 | fn next(&mut self) -> Option<Self::Item> { |
312 | loop { |
313 | let curr = self.registry.span(self.next.as_ref()?)?; |
314 | |
315 | #[cfg (all(feature = "registry" , feature = "std" ))] |
316 | let curr = curr.with_filter(self.filter); |
317 | self.next = curr.data.parent().cloned(); |
318 | |
319 | // If the `Scope` is filtered, check if the current span is enabled |
320 | // by the selected filter ID. |
321 | |
322 | #[cfg (all(feature = "registry" , feature = "std" ))] |
323 | { |
324 | if !curr.is_enabled_for(self.filter) { |
325 | // The current span in the chain is disabled for this |
326 | // filter. Try its parent. |
327 | continue; |
328 | } |
329 | } |
330 | |
331 | return Some(curr); |
332 | } |
333 | } |
334 | } |
335 | |
336 | impl<'a, R> SpanRef<'a, R> |
337 | where |
338 | R: LookupSpan<'a>, |
339 | { |
340 | /// Returns this span's ID. |
341 | pub fn id(&self) -> Id { |
342 | self.data.id() |
343 | } |
344 | |
345 | /// Returns a static reference to the span's metadata. |
346 | pub fn metadata(&self) -> &'static Metadata<'static> { |
347 | self.data.metadata() |
348 | } |
349 | |
350 | /// Returns the span's name, |
351 | pub fn name(&self) -> &'static str { |
352 | self.data.metadata().name() |
353 | } |
354 | |
355 | /// Returns a list of [fields] defined by the span. |
356 | /// |
357 | /// [fields]: tracing_core::field |
358 | pub fn fields(&self) -> &FieldSet { |
359 | self.data.metadata().fields() |
360 | } |
361 | |
362 | /// Returns a `SpanRef` describing this span's parent, or `None` if this |
363 | /// span is the root of its trace tree. |
364 | pub fn parent(&self) -> Option<Self> { |
365 | let id = self.data.parent()?; |
366 | let data = self.registry.span_data(id)?; |
367 | |
368 | #[cfg (all(feature = "registry" , feature = "std" ))] |
369 | { |
370 | // move these into mut bindings if the registry feature is enabled, |
371 | // since they may be mutated in the loop. |
372 | let mut data = data; |
373 | loop { |
374 | // Is this parent enabled by our filter? |
375 | if data.is_enabled_for(self.filter) { |
376 | return Some(Self { |
377 | registry: self.registry, |
378 | filter: self.filter, |
379 | data, |
380 | }); |
381 | } |
382 | |
383 | // It's not enabled. If the disabled span has a parent, try that! |
384 | let id = data.parent()?; |
385 | data = self.registry.span_data(id)?; |
386 | } |
387 | } |
388 | |
389 | #[cfg (not(all(feature = "registry" , feature = "std" )))] |
390 | Some(Self { |
391 | registry: self.registry, |
392 | data, |
393 | }) |
394 | } |
395 | |
396 | /// Returns an iterator over all parents of this span, starting with this span, |
397 | /// ordered from leaf to root. |
398 | /// |
399 | /// The iterator will first return the span, then the span's immediate parent, |
400 | /// followed by that span's parent, and so on, until it reaches a root span. |
401 | /// |
402 | /// ```rust |
403 | /// use tracing::{span, Subscriber}; |
404 | /// use tracing_subscriber::{ |
405 | /// layer::{Context, Layer}, |
406 | /// prelude::*, |
407 | /// registry::LookupSpan, |
408 | /// }; |
409 | /// |
410 | /// struct PrintingLayer; |
411 | /// impl<S> Layer<S> for PrintingLayer |
412 | /// where |
413 | /// S: Subscriber + for<'lookup> LookupSpan<'lookup>, |
414 | /// { |
415 | /// fn on_enter(&self, id: &span::Id, ctx: Context<S>) { |
416 | /// let span = ctx.span(id).unwrap(); |
417 | /// let scope = span.scope().map(|span| span.name()).collect::<Vec<_>>(); |
418 | /// println!("Entering span: {:?}" , scope); |
419 | /// } |
420 | /// } |
421 | /// |
422 | /// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || { |
423 | /// let _root = tracing::info_span!("root" ).entered(); |
424 | /// // Prints: Entering span: ["root"] |
425 | /// let _child = tracing::info_span!("child" ).entered(); |
426 | /// // Prints: Entering span: ["child", "root"] |
427 | /// let _leaf = tracing::info_span!("leaf" ).entered(); |
428 | /// // Prints: Entering span: ["leaf", "child", "root"] |
429 | /// }); |
430 | /// ``` |
431 | /// |
432 | /// If the opposite order (from the root to this span) is desired, calling [`Scope::from_root`] on |
433 | /// the returned iterator reverses the order. |
434 | /// |
435 | /// ```rust |
436 | /// # use tracing::{span, Subscriber}; |
437 | /// # use tracing_subscriber::{ |
438 | /// # layer::{Context, Layer}, |
439 | /// # prelude::*, |
440 | /// # registry::LookupSpan, |
441 | /// # }; |
442 | /// # struct PrintingLayer; |
443 | /// impl<S> Layer<S> for PrintingLayer |
444 | /// where |
445 | /// S: Subscriber + for<'lookup> LookupSpan<'lookup>, |
446 | /// { |
447 | /// fn on_enter(&self, id: &span::Id, ctx: Context<S>) { |
448 | /// let span = ctx.span(id).unwrap(); |
449 | /// let scope = span.scope().from_root().map(|span| span.name()).collect::<Vec<_>>(); |
450 | /// println!("Entering span: {:?}" , scope); |
451 | /// } |
452 | /// } |
453 | /// |
454 | /// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || { |
455 | /// let _root = tracing::info_span!("root" ).entered(); |
456 | /// // Prints: Entering span: ["root"] |
457 | /// let _child = tracing::info_span!("child" ).entered(); |
458 | /// // Prints: Entering span: ["root", "child"] |
459 | /// let _leaf = tracing::info_span!("leaf" ).entered(); |
460 | /// // Prints: Entering span: ["root", "child", "leaf"] |
461 | /// }); |
462 | /// ``` |
463 | pub fn scope(&self) -> Scope<'a, R> { |
464 | Scope { |
465 | registry: self.registry, |
466 | next: Some(self.id()), |
467 | |
468 | #[cfg (feature = "registry" )] |
469 | filter: self.filter, |
470 | } |
471 | } |
472 | |
473 | /// Returns a reference to this span's `Extensions`. |
474 | /// |
475 | /// The extensions may be used by `Layer`s to store additional data |
476 | /// describing the span. |
477 | #[cfg (feature = "std" )] |
478 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
479 | pub fn extensions(&self) -> Extensions<'_> { |
480 | self.data.extensions() |
481 | } |
482 | |
483 | /// Returns a mutable reference to this span's `Extensions`. |
484 | /// |
485 | /// The extensions may be used by `Layer`s to store additional data |
486 | /// describing the span. |
487 | #[cfg (feature = "std" )] |
488 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
489 | pub fn extensions_mut(&self) -> ExtensionsMut<'_> { |
490 | self.data.extensions_mut() |
491 | } |
492 | |
493 | #[cfg (all(feature = "registry" , feature = "std" ))] |
494 | pub(crate) fn try_with_filter(self, filter: FilterId) -> Option<Self> { |
495 | if self.is_enabled_for(filter) { |
496 | return Some(self.with_filter(filter)); |
497 | } |
498 | |
499 | None |
500 | } |
501 | |
502 | #[inline ] |
503 | #[cfg (all(feature = "registry" , feature = "std" ))] |
504 | pub(crate) fn is_enabled_for(&self, filter: FilterId) -> bool { |
505 | self.data.is_enabled_for(filter) |
506 | } |
507 | |
508 | #[inline ] |
509 | #[cfg (all(feature = "registry" , feature = "std" ))] |
510 | fn with_filter(self, filter: FilterId) -> Self { |
511 | Self { filter, ..self } |
512 | } |
513 | } |
514 | |
515 | #[cfg (all(test, feature = "registry" , feature = "std" ))] |
516 | mod tests { |
517 | use crate::{ |
518 | layer::{Context, Layer}, |
519 | prelude::*, |
520 | registry::LookupSpan, |
521 | }; |
522 | use std::sync::{Arc, Mutex}; |
523 | use tracing::{span, Subscriber}; |
524 | |
525 | #[test] |
526 | fn spanref_scope_iteration_order() { |
527 | let last_entered_scope = Arc::new(Mutex::new(Vec::new())); |
528 | |
529 | #[derive(Default)] |
530 | struct PrintingLayer { |
531 | last_entered_scope: Arc<Mutex<Vec<&'static str>>>, |
532 | } |
533 | |
534 | impl<S> Layer<S> for PrintingLayer |
535 | where |
536 | S: Subscriber + for<'lookup> LookupSpan<'lookup>, |
537 | { |
538 | fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { |
539 | let span = ctx.span(id).unwrap(); |
540 | let scope = span.scope().map(|span| span.name()).collect::<Vec<_>>(); |
541 | *self.last_entered_scope.lock().unwrap() = scope; |
542 | } |
543 | } |
544 | |
545 | let _guard = tracing::subscriber::set_default(crate::registry().with(PrintingLayer { |
546 | last_entered_scope: last_entered_scope.clone(), |
547 | })); |
548 | |
549 | let _root = tracing::info_span!("root" ).entered(); |
550 | assert_eq!(&*last_entered_scope.lock().unwrap(), &["root" ]); |
551 | let _child = tracing::info_span!("child" ).entered(); |
552 | assert_eq!(&*last_entered_scope.lock().unwrap(), &["child" , "root" ]); |
553 | let _leaf = tracing::info_span!("leaf" ).entered(); |
554 | assert_eq!( |
555 | &*last_entered_scope.lock().unwrap(), |
556 | &["leaf" , "child" , "root" ] |
557 | ); |
558 | } |
559 | |
560 | #[test] |
561 | fn spanref_scope_fromroot_iteration_order() { |
562 | let last_entered_scope = Arc::new(Mutex::new(Vec::new())); |
563 | |
564 | #[derive(Default)] |
565 | struct PrintingLayer { |
566 | last_entered_scope: Arc<Mutex<Vec<&'static str>>>, |
567 | } |
568 | |
569 | impl<S> Layer<S> for PrintingLayer |
570 | where |
571 | S: Subscriber + for<'lookup> LookupSpan<'lookup>, |
572 | { |
573 | fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { |
574 | let span = ctx.span(id).unwrap(); |
575 | let scope = span |
576 | .scope() |
577 | .from_root() |
578 | .map(|span| span.name()) |
579 | .collect::<Vec<_>>(); |
580 | *self.last_entered_scope.lock().unwrap() = scope; |
581 | } |
582 | } |
583 | |
584 | let _guard = tracing::subscriber::set_default(crate::registry().with(PrintingLayer { |
585 | last_entered_scope: last_entered_scope.clone(), |
586 | })); |
587 | |
588 | let _root = tracing::info_span!("root" ).entered(); |
589 | assert_eq!(&*last_entered_scope.lock().unwrap(), &["root" ]); |
590 | let _child = tracing::info_span!("child" ).entered(); |
591 | assert_eq!(&*last_entered_scope.lock().unwrap(), &["root" , "child" ,]); |
592 | let _leaf = tracing::info_span!("leaf" ).entered(); |
593 | assert_eq!( |
594 | &*last_entered_scope.lock().unwrap(), |
595 | &["root" , "child" , "leaf" ] |
596 | ); |
597 | } |
598 | } |
599 | |