tracing_tunnel/
types.rs

1//! Types to carry tracing events over the WASM client-host boundary.
2
3use core::hash::Hash;
4#[cfg(feature = "std")]
5use std::path;
6
7use serde::{Deserialize, Serialize};
8use tracing_core::{Level, Metadata};
9
10use crate::{
11    alloc::{BTreeMap, Cow, String, Vec},
12    TracedValues,
13};
14
15/// ID of a tracing [`Metadata`] record as used in [`TracingEvent`]s.
16pub type MetadataId = u64;
17/// ID of a tracing span as used in [`TracingEvent`]s.
18pub type RawSpanId = u64;
19
20/// Tracing level defined in [`CallSiteData`].
21///
22/// This corresponds to [`Level`] from the `tracing-core` library, but is (de)serializable.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum TracingLevel {
26    /// "ERROR" level.
27    Error,
28    /// "WARN" level.
29    Warn,
30    /// "INFO" level.
31    Info,
32    /// "DEBUG" level.
33    Debug,
34    /// "TRACE" level.
35    Trace,
36}
37
38impl From<Level> for TracingLevel {
39    fn from(level: Level) -> Self {
40        match level {
41            Level::ERROR => Self::Error,
42            Level::WARN => Self::Warn,
43            Level::INFO => Self::Info,
44            Level::DEBUG => Self::Debug,
45            Level::TRACE => Self::Trace,
46        }
47    }
48}
49
50/// Kind of [`CallSiteData`] location: either a span, or an event.
51#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum CallSiteKind {
54    /// Call site is a span.
55    Span,
56    /// Call site is an event.
57    Event,
58}
59
60/// Data for a single tracing call site: either a span definition, or an event definition.
61///
62/// This corresponds to [`Metadata`] from the `tracing-core` library, but is (de)serializable.
63#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
64pub struct CallSiteData {
65    /// Kind of the call site.
66    pub kind: CallSiteKind,
67    /// Name of the call site.
68    pub name: Cow<'static, str>,
69    /// Tracing target.
70    pub target: Cow<'static, str>,
71    /// Tracing level.
72    pub level: TracingLevel,
73    /// Path to the module where this call site is defined.
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub module_path: Option<Cow<'static, str>>,
76    /// Path to the file where this call site is defined.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub file: Option<Cow<'static, str>>,
79    /// Line number for this call site.
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub line: Option<u32>,
82    /// Fields defined by this call site.
83    pub fields: Vec<Cow<'static, str>>,
84}
85
86impl From<&Metadata<'static>> for CallSiteData {
87    fn from(metadata: &Metadata<'static>) -> Self {
88        let kind = if metadata.is_span() {
89            CallSiteKind::Span
90        } else {
91            debug_assert!(metadata.is_event());
92            CallSiteKind::Event
93        };
94        let fields = metadata
95            .fields()
96            .iter()
97            .map(|field| Cow::Borrowed(field.name()));
98
99        Self {
100            kind,
101            name: Cow::Borrowed(metadata.name()),
102            target: Cow::Borrowed(metadata.target()),
103            level: TracingLevel::from(*metadata.level()),
104            module_path: metadata.module_path().map(Cow::Borrowed),
105            file: metadata.file().map(Cow::Borrowed),
106            line: metadata.line(),
107            fields: fields.collect(),
108        }
109    }
110}
111
112/// Event produced during tracing.
113///
114/// These events are emitted by a [`TracingEventSender`] and then consumed
115/// by a [`TracingEventReceiver`] to pass tracing info across an API boundary.
116///
117/// [`TracingEventSender`]: crate::TracingEventSender
118/// [`TracingEventReceiver`]: crate::TracingEventReceiver
119#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121#[non_exhaustive]
122pub enum TracingEvent {
123    /// New call site.
124    NewCallSite {
125        /// Unique ID of the call site that will be used to refer to it in the following events.
126        id: MetadataId,
127        /// Information about the call site.
128        #[serde(flatten)]
129        data: CallSiteData,
130    },
131
132    /// New tracing span.
133    NewSpan {
134        /// Unique ID of the span that will be used to refer to it in the following events.
135        id: RawSpanId,
136        /// Parent span ID. `None` means using the contextual parent (i.e., the current span).
137        #[serde(default, skip_serializing_if = "Option::is_none")]
138        parent_id: Option<RawSpanId>,
139        /// ID of the span metadata.
140        metadata_id: MetadataId,
141        /// Values associated with the span.
142        values: TracedValues<String>,
143    },
144    /// New "follows from" relation between spans.
145    FollowsFrom {
146        /// ID of the follower span.
147        id: RawSpanId,
148        /// ID of the source span.
149        follows_from: RawSpanId,
150    },
151    /// Span was entered.
152    SpanEntered {
153        /// ID of the span.
154        id: RawSpanId,
155    },
156    /// Span was exited.
157    SpanExited {
158        /// ID of the span.
159        id: RawSpanId,
160    },
161    /// Span was cloned.
162    SpanCloned {
163        /// ID of the span.
164        id: RawSpanId,
165    },
166    /// Span was dropped (aka closed).
167    SpanDropped {
168        /// ID of the span.
169        id: RawSpanId,
170    },
171    /// New values recorded for a span.
172    ValuesRecorded {
173        /// ID of the span.
174        id: RawSpanId,
175        /// Recorded values.
176        values: TracedValues<String>,
177    },
178
179    /// New event.
180    NewEvent {
181        /// ID of the event metadata.
182        metadata_id: MetadataId,
183        /// Parent span ID. `None` means using the contextual parent (i.e., the current span).
184        #[serde(default, skip_serializing_if = "Option::is_none")]
185        parent: Option<RawSpanId>,
186        /// Values associated with the event.
187        values: TracedValues<String>,
188    },
189}
190
191impl TracingEvent {
192    /// Normalizes a captured sequence of events so that it does not contain information that
193    /// changes between program runs (e.g., metadata IDs) or due to minor refactoring
194    /// (source code lines). Normalized events can be used for snapshot testing
195    /// and other purposes when reproducibility is important.
196    pub fn normalize(events: &mut [Self]) {
197        let mut metadata_id_mapping = BTreeMap::new();
198        for event in events {
199            match event {
200                TracingEvent::NewCallSite { id, data } => {
201                    // Replace metadata ID to be predictable.
202                    let new_metadata_id = metadata_id_mapping.len() as MetadataId;
203                    metadata_id_mapping.insert(*id, new_metadata_id);
204                    *id = new_metadata_id;
205                    // Normalize file paths to have `/` path delimiters.
206                    #[cfg(feature = "std")]
207                    if path::MAIN_SEPARATOR != '/' {
208                        data.file = data
209                            .file
210                            .as_deref()
211                            .map(|path| path.replace(path::MAIN_SEPARATOR, "/").into());
212                    }
213                    // Make event data not depend on specific lines, which could easily
214                    // change due to refactoring etc.
215                    data.line = None;
216                    if matches!(data.kind, CallSiteKind::Event) {
217                        data.name = Cow::Borrowed("event");
218                    }
219                }
220                TracingEvent::NewSpan { metadata_id, .. }
221                | TracingEvent::NewEvent { metadata_id, .. } => {
222                    let new_metadata_id = metadata_id_mapping.len() as MetadataId;
223                    *metadata_id = *metadata_id_mapping
224                        .entry(*metadata_id)
225                        .or_insert(new_metadata_id);
226                }
227                _ => { /* No changes */ }
228            }
229        }
230    }
231}