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");