libftrace/
lib.rs

1//! Extemely simple function tracer, useful for debugging.
2//!
3//! # Overview
4//!
5//! `libftrace` is a very simple library, focused on giving a complete overview
6//! into the execution path of Rust programs, by emitting spans and events.
7//!
8//! Similar to the much larger [`tracing`] crate, `libftrace` has *spans* and
9//! *events*.
10//!
11//! [`tracing`]: https://docs.rs/tracing/latest/tracing/
12//!
13//! ## Usage
14//!
15//! Before diving too deep, you should add the crate to your `Cargo.toml`:
16//! ```toml
17//! [dependencies]
18//! libftrace = "^0"
19//! ```
20//!
21//! Or use the following command to add it:
22//! ```sh
23//! cargo add libftrace
24//! ```
25//!
26//! ### Spans
27//!
28//! Spans are meant to represent a flow of execution through a program, spanning
29//! over some period of time. Each span can have zero-or-more child spans,
30//! representing sub-tasks within a larger span.
31//!
32//! When thinking of spans in common Rust programs, it's most common to mirror
33//! them up against the execution of functions, since they follow very similar
34//! logic: much like a span, a function starts execution at some point in time,
35//! perhaps executes other functions in it's body, and stops execution at some
36//! later point in time.
37//!
38//! In `libftrace`, function execution is thought to be analogous to spans, so
39//! spans are very simple to attach to a function. But, we'll talk more about
40//! that later.
41//!
42//! ### Events
43//!
44//! Unlike spans which span over a period of time, events represent a single
45//! moment in time, often inside a parent span.
46//!
47//! Events are used to signify something happening during a span. This can be
48//! an error occuring, a note-worthy change within the program or other
49//! information which can be useful.
50//!
51//! ### Macros
52//!
53//! #### Spans
54//!
55//! To mark a function as a spanning execution, you can add the `#[traced]`
56//! attribute. This provides a very easy, yet powerful, way of marking execution
57//! paths within your programs lifecycle.
58//!
59//! [`#[traced]`]: ftrace_macros::traced
60//!
61//! For example:
62//! ```rs
63//! #[traced]
64//! fn handle_request(req: Request) {
65//!     // ..
66//! }
67//! ```
68//!
69//! By default, `#[traced]` with use the [`Info`][`Level::Info`] verbosity
70//! level, if nothing else is defined. To change this, add the `level` argument
71//! to the attribute:
72//! ```
73//! use libftrace::*;
74//!
75//! #[traced(level = Debug)]
76//! pub fn my_function() {
77//!     // ...
78//! }
79//! ```
80//!
81//! #### Events
82//!
83//! Events can be created using the [`event!`] macro. It allows for a very
84//! quick way to emit some event to the subscriber:
85//! ```
86//! use libftrace::*;
87//!
88//! event!(level: Level::Info, "a new user logged in!");
89//! ```
90//!
91//! [`event!`]: crate::event!
92//!
93//! For convinience, there are also macros for each log level:
94//! ```
95//! use libftrace::*;
96//!
97//! trace!("event sent to backend");
98//! debug!("user logged in");
99//! info!("failed login attempt");
100//! warning!("non-TLS request made to backend");
101//! error!("product does not exist");
102//! ```
103//!
104//! #### Fields
105//!
106//! Both spans and fields can have fields attached to them, allow for better
107//! understanding of what is currently happening within the program. Fields
108//! effectively function as a key-value map, mapping some string-based key to a
109//! value.
110//!
111//! To attach fields with the `#[traced]` attribute, add the `fields()`
112//! argument:
113//! ```rs
114//! #[traced(level = Info, fields(method = req.method, host = req.host))]
115//! fn handle_request(req: Request) {
116//!     // ..
117//! }
118//! ```
119//!
120//! For events, the syntax is very similar:
121//! ```rs
122//! info!("failed login attempt", username = creds.username);
123//! ```
124
125use std::cell::UnsafeCell;
126use std::collections::VecDeque;
127use std::fmt::Display;
128use std::sync::OnceLock;
129
130#[macro_use]
131#[path = "enabled/macros.rs"]
132#[cfg(feature = "enabled")]
133pub mod macros;
134
135#[macro_use]
136#[path = "disabled/macros.rs"]
137#[cfg(not(feature = "enabled"))]
138pub mod macros;
139
140pub mod filter;
141mod render;
142
143pub use libftrace_macros::*;
144use owo_colors::{OwoColorize, Style, Styled};
145
146pub use crate::filter::*;
147use crate::render::{RenderContext, Renderable};
148
149#[derive(Default)]
150pub struct Subscriber {
151    depth: usize,
152    filter: Option<EnvFilter>,
153    current: VecDeque<SpanMetadata>,
154}
155
156unsafe impl Send for Subscriber {}
157unsafe impl Sync for Subscriber {}
158
159impl Subscriber {
160    /// Enter a new span, containing the given [`SpanMetadata`] instance.
161    ///
162    /// This method returns a guard for the span. When the guard is dropped,
163    /// the span is exited. If this is not intended, keep the guard in scope.
164    #[must_use = "This function returns a guard object to exit the span.
165        Dropping it immediately is probably incorrect. Make sure that the returned value
166        lives until the span is exited."]
167    pub fn enter_span(&mut self, metadata: SpanMetadata) -> Option<SpanGuard> {
168        if self.filter.as_ref().is_some_and(|f| !f.span_enabled(&metadata)) {
169            return None;
170        }
171
172        let cx = RenderContext {
173            depth: self.depth,
174            level: metadata.level,
175        };
176
177        let mut stdout = std::io::stdout();
178        metadata.render_to(&cx, &mut stdout).unwrap();
179
180        self.depth += 1;
181        self.current.push_front(metadata);
182
183        Some(SpanGuard)
184    }
185
186    /// Emit the given event in the current span.
187    pub fn event(&self, metadata: EventMetadata) {
188        let current_span = self.current.front();
189
190        if self
191            .filter
192            .as_ref()
193            .is_some_and(|f| !f.event_enabled(&metadata, current_span))
194        {
195            return;
196        }
197
198        let cx = RenderContext {
199            depth: self.depth,
200            level: metadata.level,
201        };
202
203        let mut stdout = std::io::stdout();
204        metadata.render_to(&cx, &mut stdout).unwrap();
205    }
206
207    pub fn exit_span(&mut self, _span: &SpanGuard) {
208        self.current.pop_front();
209        self.depth -= 1;
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
214pub enum Level {
215    Trace,
216    Debug,
217    Info,
218    Warn,
219    Error,
220}
221
222impl TryFrom<&str> for Level {
223    type Error = ();
224
225    fn try_from(value: &str) -> Result<Self, ()> {
226        match value.to_lowercase().as_str() {
227            "trace" => Ok(Level::Trace),
228            "debug" => Ok(Level::Debug),
229            "info" => Ok(Level::Info),
230            "warn" => Ok(Level::Warn),
231            "error" => Ok(Level::Error),
232            _ => Err(()),
233        }
234    }
235}
236
237pub struct SpanMetadata {
238    pub name: &'static str,
239    pub location: &'static std::panic::Location<'static>,
240    pub level: Level,
241    fields: FieldSet,
242}
243
244impl SpanMetadata {
245    #[track_caller]
246    pub fn new(name: &'static str, level: Level) -> Self {
247        Self {
248            name,
249            level,
250            location: std::panic::Location::caller(),
251            fields: FieldSet::default(),
252        }
253    }
254
255    pub fn with_field(mut self, key: &'static str, value: impl Display + 'static) -> Self {
256        self.fields.add(key, value);
257        self
258    }
259}
260
261pub struct EventMetadata {
262    pub message: String,
263    pub location: &'static std::panic::Location<'static>,
264    pub level: Level,
265    fields: FieldSet,
266}
267
268impl EventMetadata {
269    #[track_caller]
270    pub fn new<S: Into<String>>(message: S, level: Level) -> Self {
271        Self {
272            message: message.into(),
273            level,
274            location: std::panic::Location::caller(),
275            fields: FieldSet::default(),
276        }
277    }
278
279    pub fn with_field(mut self, key: &'static str, value: impl Display + 'static) -> Self {
280        self.fields.add(key, value);
281        self
282    }
283}
284
285#[derive(Default)]
286struct FieldSet {
287    inner: Vec<(&'static str, Value)>,
288}
289
290impl FieldSet {
291    pub fn add(&mut self, key: &'static str, value: impl Display + 'static) {
292        self.inner.push((key, Value(Box::new(value))));
293    }
294}
295
296pub struct Value(Box<dyn Display>);
297
298impl Display for Value {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        Display::fmt(self.0.as_ref(), f)
301    }
302}
303
304#[derive(Clone, PartialEq, Eq)]
305pub struct SpanGuard;
306
307impl Drop for SpanGuard {
308    fn drop(&mut self) {
309        with_subscriber(|subscriber| subscriber.exit_span(self))
310    }
311}
312
313struct Global<T> {
314    inner: UnsafeCell<T>,
315}
316
317unsafe impl<T> Sync for Global<T> where T: Send {}
318
319static GLOBAL: OnceLock<Global<Subscriber>> = OnceLock::new();
320
321pub fn with_subscriber<F: FnOnce(&mut Subscriber) -> R, R>(f: F) -> R {
322    let global = GLOBAL.get_or_init(|| Global {
323        inner: UnsafeCell::new(Subscriber::default()),
324    });
325
326    unsafe { f(&mut *global.inner.get()) }
327}
328
329/// Sets the current filter of the global trace subscriber.
330///
331/// To create a [`EnvFilter`] instance, see [`from_env`], [`from_default_env`]
332/// or [`parse`].
333pub fn set_filter(filter: EnvFilter) {
334    with_subscriber(|subscriber| subscriber.filter = Some(filter));
335}