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, TracingEventReceiver};
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//! ## Receiving events from `TracingEventReceiver`
116//!
117//! ```
118//! # use tracing_tunnel::{
119//! # LocalSpans, PersistedMetadata, PersistedSpans, TracingEvent, TracingEventReceiver
120//! # };
121//! tracing_subscriber::fmt().pretty().init();
122//!
123//! let events: Vec<TracingEvent> = // ...
124//! # vec![];
125//!
126//! let mut spans = PersistedSpans::default();
127//! let mut local_spans = LocalSpans::default();
128//! // Replay `events` using the default subscriber.
129//! let mut receiver = TracingEventReceiver::default();
130//! for event in events {
131//! if let Err(err) = receiver.try_receive(event) {
132//! tracing::warn!(%err, "received invalid tracing event");
133//! }
134//! }
135//! // Persist the resulting receiver state. There are two pieces
136//! // of the state: metadata and alive spans.
137//! let metadata = receiver.persist_metadata();
138//! let (spans, local_spans) = receiver.persist();
139//! // `metadata` can be shared among multiple executions of the same executable
140//! // (e.g., a WASM module).
141//! // `spans` and `local_spans` are specific to the execution; `spans` should
142//! // be persisted, while `local_spans` should be stored in RAM.
143//! ```
144
145#![cfg_attr(not(feature = "std"), no_std)]
146// Documentation settings.
147#![cfg_attr(docsrs, feature(doc_cfg))]
148#![doc(html_root_url = "https://docs.rs/tracing-tunnel/0.1.0")]
149// Linter settings.
150#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
151#![warn(clippy::all, clippy::pedantic)]
152#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]
153
154#[cfg(feature = "receiver")]
155#[cfg_attr(docsrs, doc(cfg(feature = "receiver")))]
156mod receiver;
157#[cfg(feature = "sender")]
158#[cfg_attr(docsrs, doc(cfg(feature = "sender")))]
159mod sender;
160mod types;
161mod value;
162mod values;
163
164// Polyfill for `alloc` types.
165mod alloc {
166 #[cfg(not(feature = "std"))]
167 extern crate alloc;
168 #[cfg(feature = "std")]
169 use std as alloc;
170
171 pub use alloc::{
172 borrow::{Cow, ToOwned},
173 format,
174 string::String,
175 vec::{self, Vec},
176 };
177}
178
179#[cfg(feature = "receiver")]
180pub use crate::receiver::{
181 LocalSpans, PersistedMetadata, PersistedSpans, ReceiveError, TracingEventReceiver,
182};
183#[cfg(feature = "sender")]
184pub use crate::sender::TracingEventSender;
185#[cfg(feature = "std")]
186pub use crate::value::TracedError;
187pub use crate::{
188 types::{CallSiteData, CallSiteKind, MetadataId, RawSpanId, TracingEvent, TracingLevel},
189 value::{DebugObject, FromTracedValue, TracedValue},
190 values::{TracedValues, TracedValuesIter},
191};
192
193#[cfg(doctest)]
194doc_comment::doctest!("../README.md");