🛈 Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

tracing_mock/
ancestry.rs

1//! Define the ancestry of an event or span.
2//!
3//! See the documentation on the [`ExpectedAncestry`] enum for further details.
4
5use tracing_core::{
6    span::{self, Attributes},
7    Event,
8};
9
10use crate::span::{ActualSpan, ExpectedSpan};
11
12/// The ancestry of an event or span.
13///
14/// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise,
15/// an event or span may have a contextually assigned parent or in the final case will be a
16/// contextual root.
17#[derive(Debug, Eq, PartialEq)]
18pub enum ExpectedAncestry {
19    /// The event or span has an explicitly assigned parent (created with `parent: span_id`) span.
20    HasExplicitParent(ExpectedSpan),
21    /// The event or span is an explicitly defined root. It was created with `parent: None` and
22    /// has no parent.
23    IsExplicitRoot,
24    /// The event or span has a contextually assigned parent span. It has no explicitly assigned
25    /// parent span, nor has it been explicitly defined as a root (it was created without the
26    /// `parent:` directive). There was a span in context when this event or span was created.
27    HasContextualParent(ExpectedSpan),
28    /// The event or span is a contextual root. It has no explicitly assigned parent, nor has it
29    /// been explicitly defined as a root (it was created without the `parent:` directive).
30    /// Additionally, no span was in context when this event or span was created.
31    IsContextualRoot,
32}
33
34pub(crate) enum ActualAncestry {
35    HasExplicitParent(ActualSpan),
36    IsExplicitRoot,
37    HasContextualParent(ActualSpan),
38    IsContextualRoot,
39}
40
41impl ExpectedAncestry {
42    #[track_caller]
43    pub(crate) fn check(
44        &self,
45        actual_ancestry: &ActualAncestry,
46        ctx: impl std::fmt::Display,
47        collector_name: &str,
48    ) {
49        match (self, actual_ancestry) {
50            (Self::IsExplicitRoot, ActualAncestry::IsExplicitRoot) => {}
51            (Self::IsContextualRoot, ActualAncestry::IsContextualRoot) => {}
52            (
53                Self::HasExplicitParent(expected_parent),
54                ActualAncestry::HasExplicitParent(actual_parent),
55            ) => {
56                expected_parent.check(
57                    actual_parent,
58                    format_args!("{ctx} to have an explicit parent span"),
59                    collector_name,
60                );
61            }
62            (
63                Self::HasContextualParent(expected_parent),
64                ActualAncestry::HasContextualParent(actual_parent),
65            ) => {
66                println!("----> [{collector_name}] check {expected_parent:?} against actual parent with Id={id:?}", id = actual_parent.id());
67                expected_parent.check(
68                    actual_parent,
69                    format_args!("{ctx} to have a contextual parent span"),
70                    collector_name,
71                );
72            }
73            _ => {
74                // Ancestry types don't match at all.
75                let expected_description = match self {
76                    Self::IsExplicitRoot => "be an explicit root",
77                    Self::HasExplicitParent(_) => "have an explicit parent span",
78                    Self::IsContextualRoot => "be a contextual root",
79                    Self::HasContextualParent(_) => "have a contextual parent span",
80                };
81
82                let actual_description = match actual_ancestry {
83                    ActualAncestry::IsExplicitRoot => "is actually an explicit root",
84                    ActualAncestry::HasExplicitParent(_) => "actually has an explicit parent span",
85                    ActualAncestry::IsContextualRoot => "is actually a contextual root",
86                    ActualAncestry::HasContextualParent(_) => {
87                        "actually has a contextual parent span"
88                    }
89                };
90
91                panic!(
92                    "{}",
93                    format!(
94                        "[{collector_name}] expected {ctx} to {expected_description}, \
95                        but it {actual_description}"
96                    )
97                );
98            }
99        }
100    }
101}
102
103pub(crate) trait HasAncestry {
104    fn is_contextual(&self) -> bool;
105
106    fn is_root(&self) -> bool;
107
108    fn parent(&self) -> Option<&span::Id>;
109}
110
111impl HasAncestry for &Event<'_> {
112    fn is_contextual(&self) -> bool {
113        (self as &Event<'_>).is_contextual()
114    }
115
116    fn is_root(&self) -> bool {
117        (self as &Event<'_>).is_root()
118    }
119
120    fn parent(&self) -> Option<&span::Id> {
121        (self as &Event<'_>).parent()
122    }
123}
124
125impl HasAncestry for &Attributes<'_> {
126    fn is_contextual(&self) -> bool {
127        (self as &Attributes<'_>).is_contextual()
128    }
129
130    fn is_root(&self) -> bool {
131        (self as &Attributes<'_>).is_root()
132    }
133
134    fn parent(&self) -> Option<&span::Id> {
135        (self as &Attributes<'_>).parent()
136    }
137}
138
139/// Determines the ancestry of an actual span or event.
140///
141/// The rules for determining the ancestry are as follows:
142///
143/// +------------+--------------+-----------------+---------------------+
144/// | Contextual | Current Span | Explicit Parent | Ancestry            |
145/// +------------+--------------+-----------------+---------------------+
146/// | Yes        | Yes          | -               | HasContextualParent |
147/// | Yes        | No           | -               | IsContextualRoot    |
148/// | No         | -            | Yes             | HasExplicitParent   |
149/// | No         | -            | No              | IsExplicitRoot      |
150/// +------------+--------------+-----------------+---------------------+
151pub(crate) fn get_ancestry(
152    item: impl HasAncestry,
153    lookup_current: impl FnOnce() -> Option<span::Id>,
154    actual_span: impl FnOnce(&span::Id) -> Option<ActualSpan>,
155) -> ActualAncestry {
156    if item.is_contextual() {
157        if let Some(parent_id) = lookup_current() {
158            let contextual_parent_span = actual_span(&parent_id).expect(
159                "tracing-mock: contextual parent cannot \
160                            be looked up by ID. Was it recorded correctly?",
161            );
162            ActualAncestry::HasContextualParent(contextual_parent_span)
163        } else {
164            ActualAncestry::IsContextualRoot
165        }
166    } else if item.is_root() {
167        ActualAncestry::IsExplicitRoot
168    } else {
169        let parent_id = item.parent().expect(
170            "tracing-mock: is_contextual=false is_root=false \
171                        but no explicit parent found. This is a bug!",
172        );
173        let explicit_parent_span = actual_span(parent_id).expect(
174            "tracing-mock: explicit parent cannot be looked \
175                        up by ID. Is the provided Span ID valid: {parent_id}",
176        );
177        ActualAncestry::HasExplicitParent(explicit_parent_span)
178    }
179}