tracing_capture/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
//! Capturing tracing spans and events, e.g. for testing purposes.
//!
//! The core type in this crate is [`CaptureLayer`], a tracing [`Layer`] that can be used
//! to capture tracing spans and events.
//!
//! [`Layer`]: tracing_subscriber::Layer
//!
//! # Examples
//!
//! ```
//! use tracing::Level;
//! use tracing_subscriber::layer::SubscriberExt;
//! use tracing_capture::{CaptureLayer, SharedStorage};
//!
//! let subscriber = tracing_subscriber::fmt()
//!     .pretty()
//!     .with_max_level(Level::INFO)
//!     .finish();
//! // Add the capturing layer.
//! let storage = SharedStorage::default();
//! let subscriber = subscriber.with(CaptureLayer::new(&storage));
//!
//! // Capture tracing information.
//! tracing::subscriber::with_default(subscriber, || {
//!     tracing::info_span!("test", num = 42_i64).in_scope(|| {
//!         tracing::warn!("I feel disturbance in the Force...");
//!     });
//! });
//!
//! // Inspect the only captured span.
//! let storage = storage.lock();
//! assert_eq!(storage.all_spans().len(), 1);
//! let span = storage.all_spans().next().unwrap();
//! assert_eq!(span["num"], 42_i64);
//! assert_eq!(span.stats().entered, 1);
//! assert!(span.stats().is_closed);
//!
//! // Inspect the only event in the span.
//! let event = span.events().next().unwrap();
//! assert_eq!(*event.metadata().level(), Level::WARN);
//! assert_eq!(
//!     event.message(),
//!     Some("I feel disturbance in the Force...")
//! );
//! ```
//!
//! # Alternatives / similar tools
//!
//! - [`tracing-test`] is a lower-level alternative.
//! - [`tracing-fluent-assertions`] is more similar in its goals, but differs significantly
//!   in the API design; e.g., the assertions need to be declared before the capture.
//!
//! [`tracing-test`]: https://docs.rs/tracing-test
//! [`tracing-fluent-assertions`]: https://docs.rs/tracing-fluent-assertions

// Documentation settings.
#![doc(html_root_url = "https://docs.rs/tracing-capture/0.2.0-beta.1")]
// Linter settings.
#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]

use tracing_core::Metadata;

use std::{cmp, fmt, ops, ptr};

mod iter;
mod layer;
pub mod predicates;

pub use crate::{
    iter::{CapturedEvents, CapturedSpans, DescendantEvents, DescendantSpans},
    layer::{CaptureLayer, SharedStorage, Storage},
};

use tracing_tunnel::{TracedValue, TracedValues};

mod sealed {
    pub trait Sealed {}
}

#[derive(Debug)]
struct CapturedEventInner {
    metadata: &'static Metadata<'static>,
    values: TracedValues<&'static str>,
    id: CapturedEventId,
    parent_id: Option<CapturedSpanId>,
}

type CapturedEventId = id_arena::Id<CapturedEventInner>;

/// Captured tracing event containing a reference to its [`Metadata`] and values that the event
/// was created with.
///
/// `CapturedEvent`s are comparable and are [partially ordered](PartialOrd) according
/// to the capture order. Events are considered equal iff both are aliases of the same event;
/// i.e., equality is reference-based rather than content-based.
/// Two events from different [`Storage`]s are not ordered and are always non-equal.
///
/// Values recorded with the event can be accessed by indexing or using [`Self::value()`],
/// or iterated over using [`Self::values()`].
///
/// # Examples
///
/// ```
/// # use tracing_core::Level;
/// # use tracing_capture::CapturedEvent;
/// # fn test_wrapper(event: CapturedEvent) {
/// let event: CapturedEvent = // ...
/// #   event;
/// // Accessing event metadata and fields:
/// assert_eq!(*event.metadata().level(), Level::INFO);
/// assert_eq!(event["return"], 42_u64);
/// assert_eq!(event.message(), Some("finished computations"));
/// assert!(event.value("err").is_none());
/// // Filtering unsigned integer values:
/// let numbers = event.values().filter_map(|(_, val)| val.as_uint());
///
/// // Accessing the parent span:
/// let parent_name = event.parent().unwrap().metadata().name();
/// assert!(event
///     .ancestors()
///     .any(|span| span.metadata().name() == "test"));
/// # }
/// ```
#[derive(Clone, Copy)]
pub struct CapturedEvent<'a> {
    inner: &'a CapturedEventInner,
    storage: &'a Storage,
}

impl fmt::Debug for CapturedEvent<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.inner, formatter)
    }
}

impl<'a> CapturedEvent<'a> {
    /// Provides a reference to the event metadata.
    pub fn metadata(&self) -> &'static Metadata<'static> {
        self.inner.metadata
    }

    /// Iterates over values associated with the event.
    pub fn values(&self) -> impl Iterator<Item = (&'a str, &'a TracedValue)> + 'a {
        self.inner.values.iter()
    }

    /// Returns a value for the specified field, or `None` if the value is not defined.
    pub fn value(&self, name: &str) -> Option<&'a TracedValue> {
        self.inner.values.get(name)
    }

    /// Returns the message recorded in this event, i.e., the value of the `message` field
    /// if it has a string presentation.
    pub fn message(&self) -> Option<&'a str> {
        self.value("message").and_then(|message| match message {
            TracedValue::Object(obj) => Some(obj.as_ref()),
            TracedValue::String(s) => Some(s),
            TracedValue::Error(err) => Some(&err.message),
            _ => None,
        })
    }

    /// Returns the parent span for this event, or `None` if is not tied to a captured span.
    pub fn parent(&self) -> Option<CapturedSpan<'a>> {
        self.inner.parent_id.map(|id| self.storage.span(id))
    }

    /// Returns the references to the ancestor spans, starting from the direct parent
    /// and ending in one of [root spans](Storage::root_spans()).
    pub fn ancestors(&self) -> impl Iterator<Item = CapturedSpan<'a>> + '_ {
        std::iter::successors(self.parent(), CapturedSpan::parent)
    }
}

impl PartialEq for CapturedEvent<'_> {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self.storage, other.storage) && self.inner.id == other.inner.id
    }
}

impl Eq for CapturedEvent<'_> {}

impl PartialOrd for CapturedEvent<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        if ptr::eq(self.storage, other.storage) {
            Some(self.inner.id.cmp(&other.inner.id))
        } else {
            None
        }
    }
}

impl ops::Index<&str> for CapturedEvent<'_> {
    type Output = TracedValue;

    fn index(&self, index: &str) -> &Self::Output {
        self.value(index)
            .unwrap_or_else(|| panic!("field `{index}` is not contained in event"))
    }
}

/// Statistics about a [`CapturedSpan`].
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct SpanStats {
    /// Number of times the span was entered.
    pub entered: usize,
    /// Number of times the span was exited.
    pub exited: usize,
    /// Is the span closed (dropped)?
    pub is_closed: bool,
}

#[derive(Debug)]
struct CapturedSpanInner {
    metadata: &'static Metadata<'static>,
    values: TracedValues<&'static str>,
    stats: SpanStats,
    id: CapturedSpanId,
    parent_id: Option<CapturedSpanId>,
    child_ids: Vec<CapturedSpanId>,
    event_ids: Vec<CapturedEventId>,
    follows_from_ids: Vec<CapturedSpanId>,
}

type CapturedSpanId = id_arena::Id<CapturedSpanInner>;

/// Captured tracing span containing a reference to its [`Metadata`], values that the span
/// was created with, [stats](SpanStats), and descendant [`CapturedEvent`]s.
///
/// `CapturedSpan`s are comparable and are [partially ordered](PartialOrd) according
/// to the capture order. Spans are considered equal iff both are aliases of the same span;
/// i.e., equality is reference-based rather than content-based.
/// Two spans from different [`Storage`]s are not ordered and are always non-equal.
///
/// Values recorded with the span can be accessed by indexing or using [`Self::value()`],
/// or iterated over using [`Self::values()`].
///
/// # Examples
///
/// ```
/// # use tracing_core::Level;
/// # use tracing_capture::CapturedSpan;
/// # fn test_wrapper(span: CapturedSpan) {
/// let span: CapturedSpan = // ...
/// #   span;
/// // Accessing event metadata and fields:
/// assert_eq!(*span.metadata().level(), Level::INFO);
/// assert_eq!(span["arg"], 42_u64);
/// assert!(span.value("other_arg").is_none());
/// // Filtering unsigned integer values:
/// let numbers = span.values().filter_map(|(_, val)| val.as_uint());
///
/// // Accessing the parent span:
/// let parent_name = span.parent().unwrap().metadata().name();
/// assert!(span
///     .ancestors()
///     .any(|span| span.metadata().name() == "test"));
///
/// // Accessing child spans and events:
/// assert!(span.children().len() > 0);
/// let child_messages: Vec<&str> = span
///     .events()
///     .filter_map(|event| event.message())
///     .collect();
/// let descendant_span =
///     span.descendants().find(|span| span["input"] == "!").unwrap();
/// # }
/// ```
#[derive(Clone, Copy)]
pub struct CapturedSpan<'a> {
    inner: &'a CapturedSpanInner,
    storage: &'a Storage,
}

impl fmt::Debug for CapturedSpan<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.inner, formatter)
    }
}

impl<'a> CapturedSpan<'a> {
    /// Provides a reference to the span metadata.
    pub fn metadata(&self) -> &'static Metadata<'static> {
        self.inner.metadata
    }

    /// Iterates over values that the span was created with, or which were recorded later.
    pub fn values(&self) -> impl Iterator<Item = (&'a str, &'a TracedValue)> + 'a {
        self.inner.values.iter()
    }

    /// Returns a value for the specified field, or `None` if the value is not defined.
    pub fn value(&self, name: &str) -> Option<&'a TracedValue> {
        self.inner.values.get(name)
    }

    /// Returns statistics about span operations.
    pub fn stats(&self) -> SpanStats {
        self.inner.stats
    }

    /// Returns events attached to this span.
    pub fn events(&self) -> CapturedEvents<'a> {
        CapturedEvents::from_slice(self.storage, &self.inner.event_ids)
    }

    /// Returns the reference to the parent span, if any.
    pub fn parent(&self) -> Option<Self> {
        self.inner.parent_id.map(|id| self.storage.span(id))
    }

    /// Returns the references to the ancestor spans, starting from the direct parent
    /// and ending in one of [root spans](Storage::root_spans()).
    pub fn ancestors(&self) -> impl Iterator<Item = CapturedSpan<'a>> + '_ {
        std::iter::successors(self.parent(), Self::parent)
    }

    /// Iterates over the direct children of this span, in the order of their capture.
    pub fn children(&self) -> CapturedSpans<'a> {
        CapturedSpans::from_slice(self.storage, &self.inner.child_ids)
    }

    /// Iterates over the descendants of this span.
    ///
    /// In the simplest case (spans are not re-entered, span parents are contextual), the iteration
    /// order is the span capture order. In the general case, no particular order is guaranteed.
    pub fn descendants(&self) -> DescendantSpans<'a> {
        DescendantSpans::new(self)
    }

    /// Iterates over the [events](CapturedEvent) of the [descendants](Self::descendants())
    /// of this span. The iteration order is not specified. The returned events do not include
    /// the events [directly attached](Self::events()) to this span; if you need them to be included,
    /// use something like `span.events().chain(span.descendant_events())`.
    pub fn descendant_events(&self) -> DescendantEvents<'a> {
        DescendantEvents::new(self)
    }

    /// Iterates over the spans this span follows from.
    pub fn follows_from(&self) -> CapturedSpans<'a> {
        CapturedSpans::from_slice(self.storage, &self.inner.follows_from_ids)
    }
}

impl PartialEq for CapturedSpan<'_> {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self.storage, other.storage) && self.inner.id == other.inner.id
    }
}

impl Eq for CapturedSpan<'_> {}

impl PartialOrd for CapturedSpan<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        if ptr::eq(self.storage, other.storage) {
            Some(self.inner.id.cmp(&other.inner.id))
        } else {
            None
        }
    }
}

impl ops::Index<&str> for CapturedSpan<'_> {
    type Output = TracedValue;

    fn index(&self, index: &str) -> &Self::Output {
        self.value(index)
            .unwrap_or_else(|| panic!("field `{index}` is not contained in span"))
    }
}

/// Uniting trait for [`CapturedSpan`]s and [`CapturedEvent`]s that allows writing generic
/// code in cases both should be supported.
pub trait Captured<'a>: Eq + PartialOrd + sealed::Sealed {
    /// Provides a reference to the span / event metadata.
    fn metadata(&self) -> &'static Metadata<'static>;
    /// Returns a value for the specified field, or `None` if the value is not defined.
    fn value(&self, name: &str) -> Option<&'a TracedValue>;
    /// Returns the reference to the parent span, if any.
    fn parent(&self) -> Option<CapturedSpan<'a>>;
}

impl sealed::Sealed for CapturedSpan<'_> {}

impl<'a> Captured<'a> for CapturedSpan<'a> {
    #[inline]
    fn metadata(&self) -> &'static Metadata<'static> {
        self.metadata()
    }

    #[inline]
    fn value(&self, name: &str) -> Option<&'a TracedValue> {
        self.value(name)
    }

    #[inline]
    fn parent(&self) -> Option<CapturedSpan<'a>> {
        self.parent()
    }
}

impl sealed::Sealed for CapturedEvent<'_> {}

impl<'a> Captured<'a> for CapturedEvent<'a> {
    #[inline]
    fn metadata(&self) -> &'static Metadata<'static> {
        self.metadata()
    }

    #[inline]
    fn value(&self, name: &str) -> Option<&'a TracedValue> {
        self.value(name)
    }

    #[inline]
    fn parent(&self) -> Option<CapturedSpan<'a>> {
        self.parent()
    }
}

#[cfg(doctest)]
doc_comment::doctest!("../README.md");