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}