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");