tracing_tunnel/
lib.rs

1//! Tunnelling tracing information across an API boundary.
2//!
3//! This crate provides [tracing] infrastructure helpers allowing to transfer tracing events
4//! across an API boundary:
5//!
6//! - [`TracingEventSender`] is a tracing [`Subscriber`] that converts tracing events
7//!   into (de)serializable presentation that can be sent elsewhere using a customizable hook.
8//! - [`TracingEventReceiver`] consumes events produced by a `TracingEventSender` and relays them
9//!   to the tracing infrastructure. It is assumed that the source of events may outlive
10//!   both the lifetime of a particular `TracingEventReceiver` instance, and the lifetime
11//!   of the program encapsulating the receiver. To deal with this, the receiver provides
12//!   the means to persist / restore its state.
13//!
14//! # When is this needed?
15//!
16//! This crate solves the problem of having *dynamic* call sites for tracing
17//! spans / events, i.e., ones not known during compilation. This may occur if call sites
18//! are defined in dynamically loaded modules, the execution of which is embedded into the program,
19//! e.g., WASM modules.
20//!
21//! It *could* be feasible to treat such a module as a separate program and
22//! collect / analyze its traces in conjunction with host traces using distributed tracing software
23//! (e.g., [OpenTelemetry] / [Jaeger]). However, this would significantly bloat the API surface
24//! of the module, bloat its dependency tree, and would arguably break encapsulation.
25//!
26//! The approach proposed in this crate keeps the module API as simple as possible: essentially,
27//! a single function to smuggle [`TracingEvent`]s through the client–host boundary.
28//! The client side (i.e., the [`TracingEventSender`]) is almost stateless;
29//! it just streams tracing events to the host, which can have tracing logic as complex as required.
30//!
31//! Another problem that this crate solves is having module executions that can outlive
32//! the host program. For example, WASM module instances can be fully persisted and resumed later,
33//! potentially after the host is restarted. To solve this, [`TracingEventReceiver`] allows
34//! persisting call site data and alive spans, and resuming from the previously saved state
35//! (notifying the tracing infra about call sites / spans if necessary).
36//!
37//! ## Use case: workflow automation
38//!
39//! Both components are used by the [Tardigrade][`tardigrade`] workflows, in case of which
40//! the API boundary is the WASM client–host boundary.
41//!
42//! - The [`tardigrade`] client library uses [`TracingEventSender`] to send tracing events
43//!   from a workflow (i.e., a WASM module instance) to the host using a WASM import function.
44//! - [The Tardigrade runtime] uses [`TracingEventReceiver`] to pass traces from the workflow
45//!   to the host tracing infrastructure.
46//!
47//! [tracing]: https://docs.rs/tracing/0.1/tracing
48//! [`Subscriber`]: tracing_core::Subscriber
49//! [OpenTelemetry]: https://opentelemetry.io/
50//! [Jaeger]: https://www.jaegertracing.io/
51//! [`tardigrade`]: https://github.com/slowli/tardigrade
52//! [The Tardigrade runtime]: https://github.com/slowli/tardigrade
53//!
54//! # Crate features
55//!
56//! Each of the two major features outlined above is gated by the corresponding opt-in feature,
57//! [`sender`](#sender) and [`receiver`](#receiver).
58//! Without these features enabled, the crate only provides data types to capture tracing data.
59//!
60//! ## `std`
61//!
62//! *(On by default)*
63//!
64//! Enables support of types from `std`, such as the `Error` trait. Propagates to [`tracing-core`],
65//! enabling `Error` support there.
66//!
67//! Even if this feature is off, the crate requires the global allocator (i.e., the `alloc` crate)
68//! and `u32` atomics.
69//!
70//! ## `sender`
71//!
72//! *(Off by default)*
73//!
74//! Provides [`TracingEventSender`].
75//!
76//! ## `receiver`
77//!
78//! *(Off by default; requires `std`)*
79//!
80//! Provides [`TracingEventReceiver`] and related types.
81//!
82//! [`tracing-core`]: https://docs.rs/tracing-core/0.1/tracing_core
83//!
84//! # Examples
85//!
86//! ## Sending events with `TracingEventSender`
87//!
88//! ```
89//! # use assert_matches::assert_matches;
90//! # use std::sync::mpsc;
91//! use tracing_tunnel::{TracingEvent, TracingEventSender};
92//!
93//! // Let's collect tracing events using an MPSC channel.
94//! let (events_sx, events_rx) = mpsc::sync_channel(10);
95//! let subscriber = TracingEventSender::new(move |event| {
96//!     events_sx.send(event).ok();
97//! });
98//!
99//! tracing::subscriber::with_default(subscriber, || {
100//!     tracing::info_span!("test", num = 42_i64).in_scope(|| {
101//!         tracing::warn!("I feel disturbance in the Force...");
102//!     });
103//! });
104//!
105//! let events: Vec<_> = events_rx.iter().collect();
106//! assert!(!events.is_empty());
107//! // There should be one "new span".
108//! let span_count = events
109//!     .iter()
110//!     .filter(|event| matches!(event, TracingEvent::NewSpan { .. }))
111//!     .count();
112//! assert_eq!(span_count, 1);
113//! ```
114//!
115//! In multithreaded environments when tracing events / spans are produced by multiple threads,
116//! you should use `TracingEventSender::sync()` constructor instead of `new()`. This will ensure that
117//! metadata registration events are always emitted before events referencing this metadata.
118//! This requires the `std` crate feature.
119//!
120//! ```
121//! # use tracing_tunnel::{TracingEvent, TracingEventSender};
122//! # use std::sync::mpsc;
123//! let (events_sx, events_rx) = mpsc::sync_channel(10);
124//! let subscriber = TracingEventSender::sync(move |event| {
125//!     events_sx.send(event).ok();
126//! });
127//! // Handle the `subscriber` and `events_rx` in the same way...
128//! ```
129//!
130//! ## Receiving events from `TracingEventReceiver`
131//!
132//! ```
133//! # use tracing_tunnel::{
134//! #     LocalSpans, PersistedMetadata, PersistedSpans, TracingEvent, TracingEventReceiver
135//! # };
136//! tracing_subscriber::fmt().pretty().init();
137//!
138//! let events: Vec<TracingEvent> = // ...
139//! #    vec![];
140//!
141//! let mut spans = PersistedSpans::default();
142//! let mut local_spans = LocalSpans::default();
143//! // Replay `events` using the default subscriber.
144//! let mut receiver = TracingEventReceiver::default();
145//! for event in events {
146//!     if let Err(err) = receiver.try_receive(event) {
147//!         tracing::warn!(%err, "received invalid tracing event");
148//!     }
149//! }
150//! // Persist the resulting receiver state. There are two pieces
151//! // of the state: metadata and alive spans.
152//! let metadata = receiver.persist_metadata();
153//! let (spans, local_spans) = receiver.persist();
154//! // `metadata` can be shared among multiple executions of the same executable
155//! // (e.g., a WASM module).
156//! // `spans` and `local_spans` are specific to the execution; `spans` should
157//! // be persisted, while `local_spans` should be stored in RAM.
158//! ```
159
160#![cfg_attr(not(feature = "std"), no_std)]
161// Documentation settings.
162#![cfg_attr(docsrs, feature(doc_cfg))]
163#![doc(html_root_url = "https://docs.rs/tracing-tunnel/0.2.0-beta.1")]
164
165#[cfg(feature = "receiver")]
166pub use crate::receiver::{
167    LocalSpans, PersistedMetadata, PersistedSpans, ReceiveError, TracingEventReceiver,
168};
169#[cfg(all(feature = "sender", feature = "std"))]
170pub use crate::sender::Synced;
171#[cfg(feature = "sender")]
172pub use crate::sender::{EventSync, TracingEventSender};
173#[cfg(feature = "std")]
174pub use crate::value::TracedError;
175pub use crate::{
176    types::{CallSiteData, CallSiteKind, MetadataId, RawSpanId, TracingEvent, TracingLevel},
177    value::{DebugObject, FromTracedValue, TracedValue},
178    values::{TracedValues, TracedValuesIter},
179};
180
181#[cfg(feature = "receiver")]
182#[cfg_attr(docsrs, doc(cfg(feature = "receiver")))]
183mod receiver;
184#[cfg(feature = "sender")]
185#[cfg_attr(docsrs, doc(cfg(feature = "sender")))]
186mod sender;
187mod types;
188mod value;
189mod values;
190
191// Polyfill for `alloc` types.
192mod alloc {
193    #[cfg(not(feature = "std"))]
194    extern crate alloc;
195    pub(crate) use alloc::{
196        borrow::{Cow, ToOwned},
197        collections::BTreeMap,
198        // ^ `HashMap` would work better, but it's not present in `alloc`
199        format,
200        string::String,
201        vec::{self, Vec},
202    };
203    #[cfg(feature = "std")]
204    use std as alloc;
205}
206
207#[cfg(doctest)]
208doc_comment::doctest!("../README.md");