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