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