1use std::fmt;
6use std::io::{self, Write};
7use std::str::FromStr;
8use std::sync::Mutex;
9use std::time::Instant;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum LogLevel {
14 Trace = 0,
15 Debug = 1,
16 Info = 2,
17 Warn = 3,
18 Error = 4,
19 Off = 5,
20}
21
22impl FromStr for LogLevel {
23 type Err = String;
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s.to_lowercase().as_str() {
27 "trace" => Ok(LogLevel::Trace),
28 "debug" => Ok(LogLevel::Debug),
29 "info" => Ok(LogLevel::Info),
30 "warn" | "warning" => Ok(LogLevel::Warn),
31 "error" => Ok(LogLevel::Error),
32 "off" | "none" => Ok(LogLevel::Off),
33 _ => Err(format!("Invalid log level: {}", s)),
34 }
35 }
36}
37
38impl LogLevel {
39 fn color_code(&self) -> &'static str {
40 match self {
41 LogLevel::Trace => "\x1b[90m", LogLevel::Debug => "\x1b[36m", LogLevel::Info => "\x1b[32m", LogLevel::Warn => "\x1b[33m", LogLevel::Error => "\x1b[31m", LogLevel::Off => "",
47 }
48 }
49
50 fn label(&self) -> &'static str {
51 match self {
52 LogLevel::Trace => "TRACE",
53 LogLevel::Debug => "DEBUG",
54 LogLevel::Info => "INFO ",
55 LogLevel::Warn => "WARN ",
56 LogLevel::Error => "ERROR",
57 LogLevel::Off => "",
58 }
59 }
60}
61
62impl fmt::Display for LogLevel {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(f, "{}", self.label().trim())
65 }
66}
67
68pub trait Logger: Send + Sync {
70 fn log(&self, level: LogLevel, target: &str, message: &str);
71 fn log_with_duration(&self, level: LogLevel, target: &str, message: &str, duration_ms: u64);
72 fn is_enabled(&self, level: LogLevel) -> bool;
73 fn level(&self) -> LogLevel;
74}
75
76pub struct GentLogger {
78 level: LogLevel,
79 writer: Mutex<Box<dyn Write + Send>>,
80 use_colors: bool,
81}
82
83impl GentLogger {
84 pub fn new(level: LogLevel) -> Self {
86 Self {
87 level,
88 writer: Mutex::new(Box::new(io::stderr())),
89 use_colors: atty::is(atty::Stream::Stderr),
90 }
91 }
92
93 pub fn with_writer(level: LogLevel, writer: Box<dyn Write + Send>) -> Self {
95 Self {
96 level,
97 writer: Mutex::new(writer),
98 use_colors: false,
99 }
100 }
101
102 fn format_message(
103 &self,
104 level: LogLevel,
105 target: &str,
106 message: &str,
107 duration_ms: Option<u64>,
108 ) -> String {
109 let reset = "\x1b[0m";
110 let dim = "\x1b[90m";
111
112 if self.use_colors {
113 let mut output = format!(
114 "{}{}{} {}{}{} {}",
115 level.color_code(),
116 level.label(),
117 reset,
118 dim,
119 target,
120 reset,
121 message,
122 );
123
124 if let Some(ms) = duration_ms {
125 output.push_str(&format!(" {}({}ms){}", dim, ms, reset));
126 }
127 output
128 } else {
129 let mut output = format!("{} {} {}", level.label(), target, message);
130 if let Some(ms) = duration_ms {
131 output.push_str(&format!(" ({}ms)", ms));
132 }
133 output
134 }
135 }
136}
137
138impl Logger for GentLogger {
139 fn log(&self, level: LogLevel, target: &str, message: &str) {
140 if level >= self.level {
141 let formatted = self.format_message(level, target, message, None);
142 if let Ok(mut writer) = self.writer.lock() {
143 let _ = writeln!(writer, "{}", formatted);
144 }
145 }
146 }
147
148 fn log_with_duration(&self, level: LogLevel, target: &str, message: &str, duration_ms: u64) {
149 if level >= self.level {
150 let formatted = self.format_message(level, target, message, Some(duration_ms));
151 if let Ok(mut writer) = self.writer.lock() {
152 let _ = writeln!(writer, "{}", formatted);
153 }
154 }
155 }
156
157 fn is_enabled(&self, level: LogLevel) -> bool {
158 level >= self.level
159 }
160
161 fn level(&self) -> LogLevel {
162 self.level
163 }
164}
165
166pub struct NullLogger;
168
169impl Logger for NullLogger {
170 fn log(&self, _level: LogLevel, _target: &str, _message: &str) {}
171 fn log_with_duration(
172 &self,
173 _level: LogLevel,
174 _target: &str,
175 _message: &str,
176 _duration_ms: u64,
177 ) {
178 }
179 fn is_enabled(&self, _level: LogLevel) -> bool {
180 false
181 }
182 fn level(&self) -> LogLevel {
183 LogLevel::Off
184 }
185}
186
187pub struct Timer<'a> {
190 start: Instant,
191 name: String,
192 target: String,
193 level: LogLevel,
194 logger: &'a dyn Logger,
195}
196
197impl<'a> Timer<'a> {
198 pub fn new(
199 name: impl Into<String>,
200 target: impl Into<String>,
201 level: LogLevel,
202 logger: &'a dyn Logger,
203 ) -> Self {
204 Self {
205 start: Instant::now(),
206 name: name.into(),
207 target: target.into(),
208 level,
209 logger,
210 }
211 }
212
213 pub fn elapsed_ms(&self) -> u64 {
214 self.start.elapsed().as_millis() as u64
215 }
216}
217
218impl<'a> Drop for Timer<'a> {
219 fn drop(&mut self) {
220 let ms = self.elapsed_ms();
221 self.logger.log_with_duration(
222 self.level,
223 &self.target,
224 &format!("{} completed", self.name),
225 ms,
226 );
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use std::sync::Arc;
234
235 #[test]
236 fn test_log_level_ordering() {
237 assert!(LogLevel::Trace < LogLevel::Debug);
238 assert!(LogLevel::Debug < LogLevel::Info);
239 assert!(LogLevel::Info < LogLevel::Warn);
240 assert!(LogLevel::Warn < LogLevel::Error);
241 assert!(LogLevel::Error < LogLevel::Off);
242 }
243
244 #[test]
245 fn test_log_level_from_str() {
246 assert_eq!("debug".parse::<LogLevel>(), Ok(LogLevel::Debug));
247 assert_eq!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug));
248 assert_eq!("info".parse::<LogLevel>(), Ok(LogLevel::Info));
249 assert_eq!("warn".parse::<LogLevel>(), Ok(LogLevel::Warn));
250 assert_eq!("warning".parse::<LogLevel>(), Ok(LogLevel::Warn));
251 assert!("invalid".parse::<LogLevel>().is_err());
252 }
253
254 #[test]
255 fn test_logger_is_enabled() {
256 let logger = GentLogger::new(LogLevel::Info);
257 assert!(!logger.is_enabled(LogLevel::Debug));
258 assert!(logger.is_enabled(LogLevel::Info));
259 assert!(logger.is_enabled(LogLevel::Warn));
260 assert!(logger.is_enabled(LogLevel::Error));
261 }
262
263 #[test]
264 fn test_null_logger() {
265 let logger = NullLogger;
266 assert!(!logger.is_enabled(LogLevel::Error));
267 logger.log(LogLevel::Error, "test", "message");
269 }
270
271 #[test]
272 fn test_logger_captures_output() {
273 use std::sync::Arc;
274
275 let buffer = Arc::new(Mutex::new(Vec::new()));
276 let buffer_clone = buffer.clone();
277
278 let writer = Box::new(TestWriter {
279 buffer: buffer_clone,
280 });
281 let logger = GentLogger::with_writer(LogLevel::Debug, writer);
282
283 logger.log(LogLevel::Info, "test", "hello world");
284
285 let output = buffer.lock().unwrap();
286 let output_str = String::from_utf8_lossy(&output);
287 assert!(output_str.contains("INFO"));
288 assert!(output_str.contains("test"));
289 assert!(output_str.contains("hello world"));
290 }
291
292 struct TestWriter {
293 buffer: Arc<Mutex<Vec<u8>>>,
294 }
295
296 impl Write for TestWriter {
297 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
298 self.buffer.lock().unwrap().extend_from_slice(buf);
299 Ok(buf.len())
300 }
301
302 fn flush(&mut self) -> io::Result<()> {
303 Ok(())
304 }
305 }
306}