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