1use 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", Level::Debug => "\x1b[36m", Level::Info => "\x1b[32m", Level::Warn => "\x1b[33m", Level::Error => "\x1b[31m", }
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 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 }
241}