Skip to main content

micromegas_tracing/
lib.rs

1//! High-performance tracing for logs, metrics, and spans.
2//!
3//! This crate provides low-overhead instrumentation (~20ns per event) for high-performance
4//! applications. Originally designed for video game engines, it focuses on predictable
5//! performance while providing comprehensive observability.
6//!
7//! # Quick Start - Instrumenting Functions
8//!
9//! The easiest way to instrument your code is with the [`#[span_fn]`](prelude::span_fn) attribute macro:
10//!
11//! ```rust,ignore
12//! use micromegas_tracing::prelude::*;
13//!
14//! #[span_fn]
15//! async fn fetch_user(id: u64) -> User {
16//!     // Automatically tracks execution time, even across .await points
17//!     database.get_user(id).await
18//! }
19//!
20//! #[span_fn]
21//! fn compute_hash(data: &[u8]) -> Hash {
22//!     // Works for sync functions too
23//!     hasher.hash(data)
24//! }
25//! ```
26//!
27//! [`#[span_fn]`](prelude::span_fn) is the primary tool for instrumenting async code. It correctly tracks
28//! wall-clock time across await points by wrapping the future in an [`InstrumentedFuture`](prelude::InstrumentedFuture).
29//!
30//! # Logging
31//!
32//! ```rust,ignore
33//! use micromegas_tracing::prelude::*;
34//!
35//! info!("User logged in");
36//! warn!("Connection timeout");
37//! error!("Failed to process request");
38//! debug!("Debug info: {value}");
39//! trace!("Detailed trace");
40//! ```
41//!
42//! # Metrics
43//!
44//! ```rust,ignore
45//! use micromegas_tracing::prelude::*;
46//!
47//! imetric!("requests_total", "count", 1);
48//! fmetric!("response_time", "ms", elapsed_ms);
49//! ```
50//!
51//! A name or unit built at run time goes through [`imetric_dyn!`] and [`fmetric_dyn!`], which
52//! copy it into the current block instead of requiring a `'static` string:
53//!
54//! ```rust,ignore
55//! imetric_dyn!(format!("queue.{queue}.depth"), "count", depth);
56//! ```
57//!
58//! # Dynamic properties
59//!
60//! Properties are `key = value` pairs, written before the format string in logs and before the
61//! value in metrics. Keys written in the macros are identifiers or string literals and are always
62//! `'static`. A literal value is interned once per callsite and costs one atomic load afterwards
63//! (unless a `..set` is spread: then the literals before it are copied into the block like dynamic
64//! values, while the spread set's own values are not). Any other value (a correlation, request or
65//! session id) is attached to the individual log entry or measure and copied into the current
66//! block, once per distinct value, and freed with it, so nothing is retained per distinct value:
67//!
68//! ```rust,ignore
69//! use micromegas_tracing::prelude::*;
70//!
71//! let cid = request.correlation_id(); // String
72//! info!(correlation_id = cid, region = "eu", rows = %n, "fetched {n} rows");
73//! imetric!("rows_fetched", "count", correlation_id = cid, region = "eu", n);
74//! fmetric!("fetch_ms", "ms", "http.method" = "GET", elapsed_ms);
75//! ```
76//!
77//! A bare value must implement [`PropertyValue`](property_value::PropertyValue) (strings do and
78//! are borrowed without a copy). `%value` formats with `Display` and `?value` with `Debug`, at
79//! the cost of one `String` allocation; those and bare values are only evaluated when the log
80//! entry is enabled. A precomputed `&'static PropertySet` is spread as the last entry with
81//! `..set`, and the pairs before it override its entries:
82//!
83//! ```rust,ignore
84//! imetric!("rows_fetched", "count", correlation_id = cid, ..SET, n);
85//! ```
86//!
87//! On the query side the values are ordinary properties:
88//! `property_get(properties, 'correlation_id')`.
89//!
90//! In analytics some property names override the row's columns: `level` and `target` for logs,
91//! `target`, `name` and `unit` for metrics. The macros reject those names as keys (`target`
92//! belongs in `target: ..` before the other arguments); an override arrives through a `..set`
93//! spread, a runtime [`DynProperty`](property_set::DynProperty) slice, or the
94//! [`imetric_dyn!`]/[`fmetric_dyn!`] macros, which set `name` and `unit`. A dynamic `level` must
95//! be a valid level name or the block fails to parse. Interop logs (the `tracing` layer) keep
96//! their level and target unchanged.
97//!
98//! # Manual Span Scopes
99//!
100//! For fine-grained control within a function, use [`span_scope!`]:
101//!
102//! ```rust,ignore
103//! use micromegas_tracing::prelude::*;
104//!
105//! fn process_batch(items: &[Item]) {
106//!     span_scope!("process_batch");
107//!
108//!     for item in items {
109//!         span_scope!("process_item");
110//!         // Process each item...
111//!     }
112//! }
113//! ```
114//!
115//! # Initialization
116//!
117//! Libraries should not initialize tracing - that's the application's responsibility.
118//! Applications typically use `#[micromegas_main]` from the `micromegas` crate, or
119//! manually set up guards:
120//!
121//! ```
122//! use micromegas_tracing::{guards, event};
123//!
124//! // Application initialization (libraries should NOT do this)
125//! let _tracing_guard = guards::TracingSystemGuard::new(
126//!     8 * 1024 * 1024,  // log buffer size
127//!     1024 * 1024,      // metrics buffer size
128//!     16 * 1024 * 1024, // spans buffer size
129//!     std::sync::Arc::new(event::NullEventSink {}),
130//!     std::collections::HashMap::new(),
131//!     true, // Enable CPU tracing
132//! );
133//! let _thread_guard = guards::TracingThreadGuard::new();
134//! ```
135//!
136//! # Architecture
137//!
138//! Unlike other tracing crates, this library collects events into a stream rather than
139//! providing per-event hooks. Events are serialized to a binary format using `transit`,
140//! enabling efficient in-process buffering and network transmission.
141//!
142
143// crate-specific lint exceptions:
144#![allow(
145    unsafe_code,
146    missing_docs,
147    clippy::missing_errors_doc,
148    clippy::inline_always
149)]
150
151// dispatch: use wasm version on wasm32
152#[cfg(not(target_arch = "wasm32"))]
153pub mod dispatch;
154#[cfg(target_arch = "wasm32")]
155#[path = "dispatch_wasm.rs"]
156pub mod dispatch;
157
158pub mod errors;
159pub mod event;
160pub mod guards;
161#[cfg(not(target_arch = "wasm32"))]
162pub mod images;
163pub mod levels;
164pub mod logs;
165pub mod metrics;
166pub mod panic_hook;
167pub mod process_info;
168pub mod property_value;
169pub mod spans;
170pub mod time;
171
172// Native-only: full on native, stubs in lib.rs for wasm32
173#[cfg(not(target_arch = "wasm32"))]
174pub mod dyn_deps;
175#[cfg(not(target_arch = "wasm32"))]
176pub mod property_set;
177#[cfg(not(target_arch = "wasm32"))]
178pub mod static_set_cache;
179
180#[cfg(target_arch = "wasm32")]
181pub mod static_set_cache {
182    use crate::property_set::PropertySet;
183
184    /// Only the empty set exists on wasm32.
185    pub struct StaticSetCache<const M: usize>;
186
187    impl<const M: usize> StaticSetCache<M> {
188        pub const fn new() -> Self {
189            Self
190        }
191
192        pub fn get(
193            &self,
194            _base: &'static PropertySet,
195            _pairs: [(&'static str, &'static str); M],
196        ) -> &'static PropertySet {
197            PropertySet::empty()
198        }
199    }
200
201    impl<const M: usize> Default for StaticSetCache<M> {
202        fn default() -> Self {
203            Self::new()
204        }
205    }
206}
207
208#[cfg(target_arch = "wasm32")]
209pub mod property_set {
210    /// Stub for EventSink trait compatibility — never constructed on wasm32
211    pub struct Property;
212    /// Stub for dispatch function signatures — only the empty set exists on wasm32
213    pub struct PropertySet;
214
215    impl PropertySet {
216        pub fn empty() -> &'static Self {
217            static EMPTY: PropertySet = PropertySet;
218            &EMPTY
219        }
220    }
221
222    /// Only the empty set exists on wasm32.
223    pub fn callsite_set(
224        _cell: &'static std::sync::OnceLock<&'static PropertySet>,
225        _pairs: &'static [(&'static str, &'static str)],
226    ) -> &'static PropertySet {
227        PropertySet::empty()
228    }
229
230    pub fn static_value(v: &'static str) -> &'static str {
231        v
232    }
233
234    /// A property whose value is not `'static`. The name is `'static` (`new`) or block-scoped
235    /// (`with_dyn_name`).
236    #[derive(Debug, Clone, Copy)]
237    pub struct DynProperty<'a> {
238        name: &'a str,
239        value: &'a str,
240    }
241
242    impl<'a> DynProperty<'a> {
243        pub const fn new(name: &'static str, value: &'a str) -> Self {
244            Self { name, value }
245        }
246
247        pub const fn with_dyn_name(name: &'a str, value: &'a str) -> Self {
248            Self { name, value }
249        }
250
251        pub fn name(&self) -> &'a str {
252            self.name
253        }
254
255        pub fn value(&self) -> &'a str {
256            self.value
257        }
258    }
259}
260
261// Native-only modules (entirely transit-dependent)
262#[cfg(not(target_arch = "wasm32"))]
263pub use dispatch::send_image;
264#[cfg(not(target_arch = "wasm32"))]
265pub mod flush_monitor;
266#[cfg(not(target_arch = "wasm32"))]
267pub mod intern_string;
268#[cfg(not(target_arch = "wasm32"))]
269pub mod parsing;
270#[cfg(not(target_arch = "wasm32"))]
271pub mod static_string_ref;
272#[cfg(not(target_arch = "wasm32"))]
273pub mod string_id;
274#[cfg(not(target_arch = "wasm32"))]
275pub mod test_utils;
276
277#[cfg(feature = "tokio")]
278pub mod runtime;
279
280#[macro_use]
281mod macros;
282
283/// Commonly used items for convenient importing - includes macros, types, and traits
284pub mod prelude {
285    pub use crate::levels::*;
286    pub use crate::process_info::*;
287    pub use crate::property_set::DynProperty;
288    #[cfg(feature = "tokio")]
289    pub use crate::runtime::TracingRuntimeExt;
290    #[cfg(feature = "tokio")]
291    pub use crate::spans::spawn_with_context;
292    pub use crate::spans::{
293        InstrumentFuture, InstrumentedFuture, InstrumentedNamedFuture, SpanContextFuture,
294        current_span_id,
295    };
296    pub use crate::time::*;
297    pub use crate::{
298        debug, error, fatal, fmetric, fmetric_dyn, imetric, imetric_dyn, info, instrument_named,
299        instrument_named_dyn, log, log_enabled, span_async_named, span_async_named_dyn, span_scope,
300        span_scope_named, span_scope_named_dyn, static_span_desc, static_span_location, trace,
301        warn,
302    };
303    pub use micromegas_tracing_proc_macros::*;
304}