avila_tracing/
lib.rs

1//! Avila Tracing - Sistema de logging nativo
2//! Substitui tracing/tracing-subscriber
3
4use std::sync::Mutex;
5
6static LOGGER: Mutex<Option<Box<dyn Logger + Send>>> = Mutex::new(None);
7
8pub trait Logger {
9    fn log(&self, level: Level, message: &str);
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Level {
14    Trace,
15    Debug,
16    Info,
17    Warn,
18    Error,
19}
20
21impl Level {
22    pub fn as_str(&self) -> &'static str {
23        match self {
24            Level::Trace => "TRACE",
25            Level::Debug => "DEBUG",
26            Level::Info => "INFO",
27            Level::Warn => "WARN",
28            Level::Error => "ERROR",
29        }
30    }
31
32    pub fn color(&self) -> &'static str {
33        match self {
34            Level::Trace => "\x1b[90m",   // Gray
35            Level::Debug => "\x1b[36m",   // Cyan
36            Level::Info => "\x1b[32m",    // Green
37            Level::Warn => "\x1b[33m",    // Yellow
38            Level::Error => "\x1b[31m",   // Red
39        }
40    }
41}
42
43pub struct ConsoleLogger {
44    min_level: Level,
45    colored: bool,
46}
47
48impl ConsoleLogger {
49    pub fn new(min_level: Level) -> Self {
50        Self {
51            min_level,
52            colored: true,
53        }
54    }
55
56    pub fn with_colors(mut self, colored: bool) -> Self {
57        self.colored = colored;
58        self
59    }
60}
61
62impl Logger for ConsoleLogger {
63    fn log(&self, level: Level, message: &str) {
64        if level < self.min_level {
65            return;
66        }
67
68        let timestamp = avila_time::DateTime::now();
69
70        if self.colored {
71            println!(
72                "{}[{}]\x1b[0m {} {}",
73                level.color(),
74                level.as_str(),
75                timestamp.format("%Y-%m-%d %H:%M:%S"),
76                message
77            );
78        } else {
79            println!(
80                "[{}] {} {}",
81                level.as_str(),
82                timestamp.format("%Y-%m-%d %H:%M:%S"),
83                message
84            );
85        }
86    }
87}
88
89pub fn init(logger: impl Logger + Send + 'static) {
90    let mut guard = LOGGER.lock().unwrap();
91    *guard = Some(Box::new(logger));
92}
93
94pub fn trace(message: &str) {
95    log(Level::Trace, message);
96}
97
98pub fn debug(message: &str) {
99    log(Level::Debug, message);
100}
101
102pub fn info(message: &str) {
103    log(Level::Info, message);
104}
105
106pub fn warn(message: &str) {
107    log(Level::Warn, message);
108}
109
110pub fn error(message: &str) {
111    log(Level::Error, message);
112}
113
114fn log(level: Level, message: &str) {
115    let guard = LOGGER.lock().unwrap();
116    if let Some(logger) = guard.as_ref() {
117        logger.log(level, message);
118    } else {
119        // Fallback: print to stderr if no logger initialized
120        eprintln!("[{}] {}", level.as_str(), message);
121    }
122}
123
124#[macro_export]
125macro_rules! trace {
126    ($($arg:tt)*) => {
127        $crate::trace(&format!($($arg)*))
128    };
129}
130
131#[macro_export]
132macro_rules! debug {
133    ($($arg:tt)*) => {
134        $crate::debug(&format!($($arg)*))
135    };
136}
137
138#[macro_export]
139macro_rules! info {
140    ($($arg:tt)*) => {
141        $crate::info(&format!($($arg)*))
142    };
143}
144
145#[macro_export]
146macro_rules! warn {
147    ($($arg:tt)*) => {
148        $crate::warn(&format!($($arg)*))
149    };
150}
151
152#[macro_export]
153macro_rules! error {
154    ($($arg:tt)*) => {
155        $crate::error(&format!($($arg)*))
156    };
157}
158
159pub struct Span {
160    name: String,
161    start: std::time::Instant,
162}
163
164impl Span {
165    pub fn new(name: &str) -> Self {
166        debug!("→ Entering span: {}", name);
167        Self {
168            name: name.to_string(),
169            start: std::time::Instant::now(),
170        }
171    }
172
173    pub fn enter(&self) {
174        debug!("→ {}", self.name);
175    }
176
177    pub fn exit(&self) {
178        let elapsed = self.start.elapsed();
179        debug!("← {} ({:?})", self.name, elapsed);
180    }
181}
182
183impl Drop for Span {
184    fn drop(&mut self) {
185        self.exit();
186    }
187}
188
189#[macro_export]
190macro_rules! span {
191    ($name:expr) => {
192        $crate::Span::new($name)
193    };
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    struct TestLogger {
201        logs: Mutex<Vec<(Level, String)>>,
202    }
203
204    impl TestLogger {
205        fn new() -> Self {
206            Self {
207                logs: Mutex::new(Vec::new()),
208            }
209        }
210
211        fn get_logs(&self) -> Vec<(Level, String)> {
212            self.logs.lock().unwrap().clone()
213        }
214    }
215
216    impl Logger for TestLogger {
217        fn log(&self, level: Level, message: &str) {
218            self.logs.lock().unwrap().push((level, message.to_string()));
219        }
220    }
221
222    #[test]
223    fn test_console_logger() {
224        let logger = ConsoleLogger::new(Level::Info);
225        logger.log(Level::Info, "test message");
226        logger.log(Level::Debug, "should not appear");
227    }
228
229    #[test]
230    fn test_levels() {
231        assert!(Level::Error > Level::Warn);
232        assert!(Level::Info > Level::Debug);
233        assert_eq!(Level::Info.as_str(), "INFO");
234    }
235
236    #[test]
237    fn test_span() {
238        let _span = Span::new("test_operation");
239        // Span should log on creation and drop
240    }
241}