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}