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}