compose_utils/
trace.rs

1#![allow(clippy::print_stdout)]
2use std::cell::RefCell;
3use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4use std::sync::LazyLock;
5use std::time::Instant;
6
7/// Global toggle to enable or disable tracing.
8pub static ENABLE_TRACE: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBool::new(false));
9
10// Per-thread indentation tracking.
11thread_local! {
12    static INDENT: RefCell<usize> = const { RefCell::new(0) };
13}
14
15static CALL_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
16
17/// Run a closure with the current indentation level.
18pub fn with_indent<F: FnOnce(usize) -> String>(f: F) {
19    INDENT.with(|level| {
20        let indent = *level.borrow();
21        println!("{}", f(indent));
22    });
23}
24
25/// Increase indentation (on function entry).
26fn indent_inc() {
27    INDENT.with(|i| *i.borrow_mut() += 1);
28}
29
30/// Decrease indentation (on function exit).
31fn indent_dec() {
32    INDENT.with(|i| *i.borrow_mut() -= 1);
33}
34
35/// Guard that logs entry/exit of a function.
36pub struct TraceFnGuard {
37    name: &'static str,
38    enabled: bool,
39    start_time: Option<Instant>,
40    id: usize,
41}
42
43impl TraceFnGuard {
44    pub fn new(name: &'static str, message: Option<&str>) -> Self {
45        let enabled = ENABLE_TRACE.load(Ordering::Relaxed);
46        let start_time = if enabled { Some(Instant::now()) } else { None };
47        let id = CALL_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
48
49        if enabled {
50            with_indent(|i| format!(
51                "{}[#{}]↳ Enter: {} {}",
52                "  ".repeat(i),
53                id,
54                name,
55                message.unwrap_or("")
56            ));
57            indent_inc();
58        }
59
60        Self {
61            name,
62            enabled,
63            start_time,
64            id,
65        }
66    }
67}
68
69impl Drop for TraceFnGuard {
70    fn drop(&mut self) {
71        if self.enabled {
72            indent_dec();
73            if let Some(start) = self.start_time {
74                let duration = start.elapsed();
75                with_indent(|i| format!(
76                    "{}[#{}]↳ Exit:  {} (took {:.2?})",
77                    "  ".repeat(i),
78                    self.id,
79                    self.name,
80                    duration
81                ));
82            } else {
83                with_indent(|i| format!(
84                    "{}↳ Exit:  {}",
85                    "  ".repeat(i),
86                    self.name
87                ));
88            }
89        }
90    }
91}
92
93/// Macro to insert tracing into functions.
94#[macro_export]
95macro_rules! trace_fn {
96    ($name:expr) => {
97        let _trace_guard = $crate::TraceFnGuard::new($name, None);
98    };
99    ($name:expr, $($tt:tt)*) => {
100        let _trace_guard = $crate::TraceFnGuard::new($name, Some(&format!($($tt)*)));
101    };
102}
103
104#[macro_export]
105macro_rules! trace_log {
106    ($($tt:tt)*) => {
107            if $crate::ENABLE_TRACE.load(std::sync::atomic::Ordering::Relaxed) {
108                $crate::with_indent(|i| format!(
109                    "{}      {}",
110                    "  ".repeat(i),
111                    format!($($tt)*),
112                ));
113            }
114    };
115}