tracing_capture/
lib.rs

1//! Capturing tracing spans and events, e.g. for testing purposes.
2//!
3//! The core type in this crate is [`CaptureLayer`], a tracing [`Layer`] that can be used
4//! to capture tracing spans and events.
5//!
6//! [`Layer`]: tracing_subscriber::Layer
7//!
8//! # Examples
9//!
10//! ```
11//! use tracing::Level;
12//! use tracing_subscriber::layer::SubscriberExt;
13//! use tracing_capture::{CaptureLayer, SharedStorage};
14//!
15//! let subscriber = tracing_subscriber::fmt()
16//!     .pretty()
17//!     .with_max_level(Level::INFO)
18//!     .finish();
19//! // Add the capturing layer.
20//! let storage = SharedStorage::default();
21//! let subscriber = subscriber.with(CaptureLayer::new(&storage));
22//!
23//! // Capture tracing information.
24//! tracing::subscriber::with_default(subscriber, || {
25//!     tracing::info_span!("test", num = 42_i64).in_scope(|| {
26//!         tracing::warn!("I feel disturbance in the Force...");
27//!     });
28//! });
29//!
30//! // Inspect the only captured span.
31//! let storage = storage.lock();
32//! assert_eq!(storage.all_spans().len(), 1);
33//! let span = storage.all_spans().next().unwrap();
34//! assert_eq!(span["num"], 42_i64);
35//! assert_eq!(span.stats().entered, 1);
36//! assert!(span.stats().is_closed);
37//!
38//! // Inspect the only event in the span.
39//! let event = span.events().next().unwrap();
40//! assert_eq!(*event.metadata().level(), Level::WARN);
41//! assert_eq!(
42//!     event.message(),
43//!     Some("I feel disturbance in the Force...")
44//! );
45//! ```
46//!
47//! # Alternatives / similar tools
48//!
49//! - [`tracing-test`] is a lower-level alternative.
50//! - [`tracing-fluent-assertions`] is more similar in its goals, but differs significantly
51//!   in the API design; e.g., the assertions need to be declared before the capture.
52//!
53//! [`tracing-test`]: https://docs.rs/tracing-test
54//! [`tracing-fluent-assertions`]: https://docs.rs/tracing-fluent-assertions
55
56// Documentation settings.
57#![doc(html_root_url = "https://docs.rs/tracing-capture/0.2.0-beta.1")]
58
59use std::{cmp, fmt, ops, ptr};
60
61use tracing_core::Metadata;
62use tracing_tunnel::{TracedValue, TracedValues};
63
64pub use crate::{
65    iter::{CapturedEvents, CapturedSpans, DescendantEvents, DescendantSpans},
66    layer::{CaptureLayer, SharedStorage, Storage},
67};
68
69mod iter;
70mod layer;
71pub mod predicates;
72
73mod sealed {
74    pub trait Sealed {}
75}
76
77#[derive(Debug)]
78struct CapturedEventInner {
79    metadata: &'static Metadata<'static>,
80    values: TracedValues<&'static str>,
81    id: CapturedEventId,
82    parent_id: Option<CapturedSpanId>,
83}
84
85type CapturedEventId = id_arena::Id<CapturedEventInner>;
86
87/// Captured tracing event containing a reference to its [`Metadata`] and values that the event
88/// was created with.
89///
90/// `CapturedEvent`s are comparable and are [partially ordered](PartialOrd) according
91/// to the capture order. Events are considered equal iff both are aliases of the same event;
92/// i.e., equality is reference-based rather than content-based.
93/// Two events from different [`Storage`]s are not ordered and are always non-equal.
94///
95/// Values recorded with the event can be accessed by indexing or using [`Self::value()`],
96/// or iterated over using [`Self::values()`].
97///
98/// # Examples
99///
100/// ```
101/// # use tracing_core::Level;
102/// # use tracing_capture::CapturedEvent;
103/// # fn test_wrapper(event: CapturedEvent) {
104/// let event: CapturedEvent = // ...
105/// #   event;
106/// // Accessing event metadata and fields:
107/// assert_eq!(*event.metadata().level(), Level::INFO);
108/// assert_eq!(event["return"], 42_u64);
109/// assert_eq!(event.message(), Some("finished computations"));
110/// assert!(event.value("err").is_none());
111/// // Filtering unsigned integer values:
112/// let numbers = event.values().filter_map(|(_, val)| val.as_uint());
113///
114/// // Accessing the parent span:
115/// let parent_name = event.parent().unwrap().metadata().name();
116/// assert!(event
117///     .ancestors()
118///     .any(|span| span.metadata().name() == "test"));
119/// # }
120/// ```
121#[derive(Clone, Copy)]
122pub struct CapturedEvent<'a> {
123    inner: &'a CapturedEventInner,
124    storage: &'a Storage,
125}
126
127impl fmt::Debug for CapturedEvent<'_> {
128    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129        fmt::Debug::fmt(&self.inner, formatter)
130    }
131}
132
133impl<'a> CapturedEvent<'a> {
134    /// Provides a reference to the event metadata.
135    pub fn metadata(&self) -> &'static Metadata<'static> {
136        self.inner.metadata
137    }
138
139    /// Iterates over values associated with the event.
140    pub fn values(&self) -> impl Iterator<Item = (&'a str, &'a TracedValue)> + 'a {
141        self.inner.values.iter()
142    }
143
144    /// Returns a value for the specified field, or `None` if the value is not defined.
145    pub fn value(&self, name: &str) -> Option<&'a TracedValue> {
146        self.inner.values.get(name)
147    }
148
149    /// Returns the message recorded in this event, i.e., the value of the `message` field
150    /// if it has a string presentation.
151    pub fn message(&self) -> Option<&'a str> {
152        self.value("message").and_then(|message| match message {
153            TracedValue::Object(obj) => Some(obj.as_ref()),
154            TracedValue::String(s) => Some(s),
155            TracedValue::Error(err) => Some(&err.message),
156            _ => None,
157        })
158    }
159
160    /// Returns the parent span for this event, or `None` if is not tied to a captured span.
161    pub fn parent(&self) -> Option<CapturedSpan<'a>> {
162        self.inner.parent_id.map(|id| self.storage.span(id))
163    }
164
165    /// Returns the references to the ancestor spans, starting from the direct parent
166    /// and ending in one of [root spans](Storage::root_spans()).
167    pub fn ancestors(&self) -> impl Iterator<Item = CapturedSpan<'a>> + '_ {
168        std::iter::successors(self.parent(), CapturedSpan::parent)
169    }
170}
171
172impl PartialEq for CapturedEvent<'_> {
173    fn eq(&self, other: &Self) -> bool {
174        ptr::eq(self.storage, other.storage) && self.inner.id == other.inner.id
175    }
176}
177
178impl Eq for CapturedEvent<'_> {}
179
180impl PartialOrd for CapturedEvent<'_> {
181    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
182        if ptr::eq(self.storage, other.storage) {
183            Some(self.inner.id.cmp(&other.inner.id))
184        } else {
185            None
186        }
187    }
188}
189
190impl ops::Index<&str> for CapturedEvent<'_> {
191    type Output = TracedValue;
192
193    fn index(&self, index: &str) -> &Self::Output {
194        self.value(index)
195            .unwrap_or_else(|| panic!("field `{index}` is not contained in event"))
196    }
197}
198
199/// Statistics about a [`CapturedSpan`].
200#[derive(Debug, Clone, Copy, Default)]
201#[non_exhaustive]
202pub struct SpanStats {
203    /// Number of times the span was entered.
204    pub entered: usize,
205    /// Number of times the span was exited.
206    pub exited: usize,
207    /// Is the span closed (dropped)?
208    pub is_closed: bool,
209}
210
211#[derive(Debug)]
212struct CapturedSpanInner {
213    metadata: &'static Metadata<'static>,
214    values: TracedValues<&'static str>,
215    stats: SpanStats,
216    id: CapturedSpanId,
217    parent_id: Option<CapturedSpanId>,
218    child_ids: Vec<CapturedSpanId>,
219    event_ids: Vec<CapturedEventId>,
220    follows_from_ids: Vec<CapturedSpanId>,
221}
222
223type CapturedSpanId = id_arena::Id<CapturedSpanInner>;
224
225/// Captured tracing span containing a reference to its [`Metadata`], values that the span
226/// was created with, [stats](SpanStats), and descendant [`CapturedEvent`]s.
227///
228/// `CapturedSpan`s are comparable and are [partially ordered](PartialOrd) according
229/// to the capture order. Spans are considered equal iff both are aliases of the same span;
230/// i.e., equality is reference-based rather than content-based.
231/// Two spans from different [`Storage`]s are not ordered and are always non-equal.
232///
233/// Values recorded with the span can be accessed by indexing or using [`Self::value()`],
234/// or iterated over using [`Self::values()`].
235///
236/// # Examples
237///
238/// ```
239/// # use tracing_core::Level;
240/// # use tracing_capture::CapturedSpan;
241/// # fn test_wrapper(span: CapturedSpan) {
242/// let span: CapturedSpan = // ...
243/// #   span;
244/// // Accessing event metadata and fields:
245/// assert_eq!(*span.metadata().level(), Level::INFO);
246/// assert_eq!(span["arg"], 42_u64);
247/// assert!(span.value("other_arg").is_none());
248/// // Filtering unsigned integer values:
249/// let numbers = span.values().filter_map(|(_, val)| val.as_uint());
250///
251/// // Accessing the parent span:
252/// let parent_name = span.parent().unwrap().metadata().name();
253/// assert!(span
254///     .ancestors()
255///     .any(|span| span.metadata().name() == "test"));
256///
257/// // Accessing child spans and events:
258/// assert!(span.children().len() > 0);
259/// let child_messages: Vec<&str> = span
260///     .events()
261///     .filter_map(|event| event.message())
262///     .collect();
263/// let descendant_span =
264///     span.descendants().find(|span| span["input"] == "!").unwrap();
265/// # }
266/// ```
267#[derive(Clone, Copy)]
268pub struct CapturedSpan<'a> {
269    inner: &'a CapturedSpanInner,
270    storage: &'a Storage,
271}
272
273impl fmt::Debug for CapturedSpan<'_> {
274    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
275        fmt::Debug::fmt(&self.inner, formatter)
276    }
277}
278
279impl<'a> CapturedSpan<'a> {
280    /// Provides a reference to the span metadata.
281    pub fn metadata(&self) -> &'static Metadata<'static> {
282        self.inner.metadata
283    }
284
285    /// Iterates over values that the span was created with, or which were recorded later.
286    pub fn values(&self) -> impl Iterator<Item = (&'a str, &'a TracedValue)> + 'a {
287        self.inner.values.iter()
288    }
289
290    /// Returns a value for the specified field, or `None` if the value is not defined.
291    pub fn value(&self, name: &str) -> Option<&'a TracedValue> {
292        self.inner.values.get(name)
293    }
294
295    /// Returns statistics about span operations.
296    pub fn stats(&self) -> SpanStats {
297        self.inner.stats
298    }
299
300    /// Returns events attached to this span.
301    pub fn events(&self) -> CapturedEvents<'a> {
302        CapturedEvents::from_slice(self.storage, &self.inner.event_ids)
303    }
304
305    /// Returns the reference to the parent span, if any.
306    pub fn parent(&self) -> Option<Self> {
307        self.inner.parent_id.map(|id| self.storage.span(id))
308    }
309
310    /// Returns the references to the ancestor spans, starting from the direct parent
311    /// and ending in one of [root spans](Storage::root_spans()).
312    pub fn ancestors(&self) -> impl Iterator<Item = CapturedSpan<'a>> + '_ {
313        std::iter::successors(self.parent(), Self::parent)
314    }
315
316    /// Iterates over the direct children of this span, in the order of their capture.
317    pub fn children(&self) -> CapturedSpans<'a> {
318        CapturedSpans::from_slice(self.storage, &self.inner.child_ids)
319    }
320
321    /// Iterates over the descendants of this span.
322    ///
323    /// In the simplest case (spans are not re-entered, span parents are contextual), the iteration
324    /// order is the span capture order. In the general case, no particular order is guaranteed.
325    pub fn descendants(&self) -> DescendantSpans<'a> {
326        DescendantSpans::new(self)
327    }
328
329    /// Iterates over the [events](CapturedEvent) of the [descendants](Self::descendants())
330    /// of this span. The iteration order is not specified. The returned events do not include
331    /// the events [directly attached](Self::events()) to this span; if you need them to be included,
332    /// use something like `span.events().chain(span.descendant_events())`.
333    pub fn descendant_events(&self) -> DescendantEvents<'a> {
334        DescendantEvents::new(self)
335    }
336
337    /// Iterates over the spans this span follows from.
338    pub fn follows_from(&self) -> CapturedSpans<'a> {
339        CapturedSpans::from_slice(self.storage, &self.inner.follows_from_ids)
340    }
341}
342
343impl PartialEq for CapturedSpan<'_> {
344    fn eq(&self, other: &Self) -> bool {
345        ptr::eq(self.storage, other.storage) && self.inner.id == other.inner.id
346    }
347}
348
349impl Eq for CapturedSpan<'_> {}
350
351impl PartialOrd for CapturedSpan<'_> {
352    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
353        if ptr::eq(self.storage, other.storage) {
354            Some(self.inner.id.cmp(&other.inner.id))
355        } else {
356            None
357        }
358    }
359}
360
361impl ops::Index<&str> for CapturedSpan<'_> {
362    type Output = TracedValue;
363
364    fn index(&self, index: &str) -> &Self::Output {
365        self.value(index)
366            .unwrap_or_else(|| panic!("field `{index}` is not contained in span"))
367    }
368}
369
370/// Uniting trait for [`CapturedSpan`]s and [`CapturedEvent`]s that allows writing generic
371/// code in cases both should be supported.
372pub trait Captured<'a>: Eq + PartialOrd + sealed::Sealed {
373    /// Provides a reference to the span / event metadata.
374    fn metadata(&self) -> &'static Metadata<'static>;
375    /// Returns a value for the specified field, or `None` if the value is not defined.
376    fn value(&self, name: &str) -> Option<&'a TracedValue>;
377    /// Returns the reference to the parent span, if any.
378    fn parent(&self) -> Option<CapturedSpan<'a>>;
379}
380
381impl sealed::Sealed for CapturedSpan<'_> {}
382
383impl<'a> Captured<'a> for CapturedSpan<'a> {
384    #[inline]
385    fn metadata(&self) -> &'static Metadata<'static> {
386        self.metadata()
387    }
388
389    #[inline]
390    fn value(&self, name: &str) -> Option<&'a TracedValue> {
391        self.value(name)
392    }
393
394    #[inline]
395    fn parent(&self) -> Option<CapturedSpan<'a>> {
396        self.parent()
397    }
398}
399
400impl sealed::Sealed for CapturedEvent<'_> {}
401
402impl<'a> Captured<'a> for CapturedEvent<'a> {
403    #[inline]
404    fn metadata(&self) -> &'static Metadata<'static> {
405        self.metadata()
406    }
407
408    #[inline]
409    fn value(&self, name: &str) -> Option<&'a TracedValue> {
410        self.value(name)
411    }
412
413    #[inline]
414    fn parent(&self) -> Option<CapturedSpan<'a>> {
415        self.parent()
416    }
417}
418
419#[cfg(doctest)]
420doc_comment::doctest!("../README.md");