nonblocking_logger/structs/
logger.rs1use crate::enums::log_level::LogLevel;
2use std::collections::HashMap;
3use std::io::{self, Write};
4use std::sync::RwLock;
5
6pub struct Logger {
8 level: LogLevel,
9 time_format: String,
10 format_strings: HashMap<LogLevel, String>,
11 targets: Vec<RwLock<Box<dyn Write + Send + Sync>>>,
12}
13
14impl Logger {
15 pub fn new() -> Self {
17 Self {
18 level: LogLevel::Info,
19 time_format: "%Y-%m-%d %H:%M:%S".to_string(),
20 format_strings: Self::default_format_strings(),
21 targets: vec![RwLock::new(Box::new(io::stdout()))],
22 }
23 }
24
25 pub fn with_level(level: LogLevel) -> Self {
27 Self {
28 level,
29 time_format: "%Y-%m-%d %H:%M:%S".to_string(),
30 format_strings: Self::default_format_strings(),
31 targets: vec![RwLock::new(Box::new(io::stdout()))],
32 }
33 }
34
35 pub fn from_env() -> Self {
43 use crate::utils::log_util::parse_log_level_from_env;
44 let level = parse_log_level_from_env();
45 Self::with_level(level)
46 }
47
48 pub fn time_format(mut self, format: &str) -> Self {
56 self.time_format = format.to_string();
57 self
58 }
59
60 pub fn no_time_prefix(mut self) -> Self {
64 self.time_format = String::new();
65 self
66 }
67
68 pub fn format_for_level(mut self, level: LogLevel, format: &str) -> Self {
70 self.format_strings.insert(level, format.to_string());
71 self
72 }
73
74 pub fn stdout(mut self) -> Self {
76 self.targets = vec![RwLock::new(Box::new(io::stdout()))];
77 self
78 }
79
80 pub fn stderr(mut self) -> Self {
82 self.targets = vec![RwLock::new(Box::new(io::stderr()))];
83 self
84 }
85
86 pub fn file(mut self, path: &str) -> io::Result<Self> {
88 let file = std::fs::OpenOptions::new()
89 .create(true)
90 .append(true)
91 .open(path)?;
92 self.targets = vec![RwLock::new(Box::new(file))];
93 Ok(self)
94 }
95
96 pub fn custom<W>(mut self, target: W) -> Self
116 where
117 W: Write + Send + Sync + 'static
118 {
119 self.targets = vec![RwLock::new(Box::new(target))];
120 self
121 }
122
123 pub fn add_stdout(mut self) -> Self {
125 self.targets.push(RwLock::new(Box::new(io::stdout())));
126 self
127 }
128
129 pub fn add_stderr(mut self) -> Self {
131 self.targets.push(RwLock::new(Box::new(io::stderr())));
132 self
133 }
134
135 pub fn add_file(mut self, path: &str) -> io::Result<Self> {
137 let file = std::fs::OpenOptions::new()
138 .create(true)
139 .append(true)
140 .open(path)?;
141 self.targets.push(RwLock::new(Box::new(file)));
142 Ok(self)
143 }
144
145 pub fn add_target<W>(mut self, target: W) -> Self
165 where
166 W: Write + Send + Sync + 'static
167 {
168 self.targets.push(RwLock::new(Box::new(target)));
169 self
170 }
171
172
173 pub fn log(&self, message: &str) -> io::Result<()> {
175 let formatted = self.format_message_simple(message);
176
177 for target in &self.targets {
178 let mut target = target.write().unwrap();
179 writeln!(target, "{}", formatted)?;
180 target.flush()?;
181 }
182
183 Ok(())
184 }
185
186 pub fn log_lazy<F>(&self, message_fn: F) -> io::Result<()>
208 where
209 F: FnOnce() -> String,
210 {
211 let message = message_fn();
212 self.log(&message)
213 }
214
215
216 pub(crate) fn log_lazy_with_level<F>(&self, level: LogLevel, message_fn: F) -> io::Result<()>
218 where
219 F: FnOnce() -> String,
220 {
221 if level < self.level {
222 return Ok(());
223 }
224
225 let message = message_fn();
226 self.log_with_level(level, &message)
227 }
228
229 pub(crate) fn log_with_level(&self, level: LogLevel, message: &str) -> io::Result<()> {
231 if level < self.level {
232 return Ok(());
233 }
234
235 let formatted = self.format_message(level, message);
236
237 for target in &self.targets {
238 let mut target = target.write().unwrap();
239 writeln!(target, "{}", formatted)?;
240 target.flush()?;
241 }
242
243 Ok(())
244 }
245
246 pub fn error(&self, message: &str) -> io::Result<()> {
248 self.log_with_level(LogLevel::Error, message)
249 }
250
251 pub fn warning(&self, message: &str) -> io::Result<()> {
252 self.log_with_level(LogLevel::Warning, message)
253 }
254
255 pub fn info(&self, message: &str) -> io::Result<()> {
256 self.log_with_level(LogLevel::Info, message)
257 }
258
259 pub fn debug(&self, message: &str) -> io::Result<()> {
260 self.log_with_level(LogLevel::Debug, message)
261 }
262
263 pub fn trace(&self, message: &str) -> io::Result<()> {
264 self.log_with_level(LogLevel::Trace, message)
265 }
266
267 pub fn error_lazy<F>(&self, message_fn: F) -> io::Result<()>
272 where
273 F: FnOnce() -> String,
274 {
275 self.log_lazy_with_level(LogLevel::Error, message_fn)
276 }
277
278 pub fn warning_lazy<F>(&self, message_fn: F) -> io::Result<()>
279 where
280 F: FnOnce() -> String,
281 {
282 self.log_lazy_with_level(LogLevel::Warning, message_fn)
283 }
284
285 pub fn info_lazy<F>(&self, message_fn: F) -> io::Result<()>
286 where
287 F: FnOnce() -> String,
288 {
289 self.log_lazy_with_level(LogLevel::Info, message_fn)
290 }
291
292 pub fn debug_lazy<F>(&self, message_fn: F) -> io::Result<()>
293 where
294 F: FnOnce() -> String,
295 {
296 self.log_lazy_with_level(LogLevel::Debug, message_fn)
297 }
298
299 pub fn trace_lazy<F>(&self, message_fn: F) -> io::Result<()>
300 where
301 F: FnOnce() -> String,
302 {
303 self.log_lazy_with_level(LogLevel::Trace, message_fn)
304 }
305
306 pub fn set_level(&mut self, level: LogLevel) {
308 self.level = level;
309 }
310
311 pub fn set_time_format(&mut self, format: &str) {
319 self.time_format = format.to_string();
320 }
321
322 pub fn disable_time_prefix(&mut self) {
326 self.time_format = String::new();
327 }
328
329 pub fn set_format_for_level(&mut self, level: LogLevel, format: &str) {
331 self.format_strings.insert(level, format.to_string());
332 }
333
334 pub fn level(&self) -> LogLevel {
336 self.level
337 }
338
339 pub fn get_level(&self) -> LogLevel {
341 self.level
342 }
343
344 pub fn clear_targets(mut self) -> Self {
346 self.targets.clear();
347 self
348 }
349
350 fn default_format_strings() -> HashMap<LogLevel, String> {
351 let mut formats = HashMap::new();
352 formats.insert(LogLevel::Error, "{time} [{level}] {message}".to_string());
353 formats.insert(LogLevel::Warning, "{time} [{level}] {message}".to_string());
354 formats.insert(LogLevel::Info, "{time} [{level}] {message}".to_string());
355 formats.insert(LogLevel::Debug, "{time} [{level}] {message}".to_string());
356 formats.insert(LogLevel::Trace, "{time} [{level}] {message}".to_string());
357 formats
358 }
359
360 fn format_message(&self, level: LogLevel, message: &str) -> String {
361 let format_string = self
362 .format_strings
363 .get(&level)
364 .unwrap_or(&self.format_strings[&LogLevel::Info])
365 .clone();
366
367 let time_str = if self.time_format.is_empty() {
368 String::new()
369 } else {
370 use simple_datetime_rs::{DateTime, Format};
371 DateTime::now()
372 .format(&self.time_format)
373 .unwrap_or_default()
374 };
375
376 format_string
377 .replace("{time}", &time_str)
378 .replace("{level}", &level.to_string())
379 .replace("{message}", message)
380 }
381
382 fn format_message_simple(&self, message: &str) -> String {
383 if self.time_format.is_empty() {
384 message.to_string()
385 } else {
386 use simple_datetime_rs::{DateTime, Format};
387 let time_str = DateTime::now()
388 .format(&self.time_format)
389 .unwrap_or_default();
390 format!("{} {}", time_str, message)
391 }
392 }
393}
394
395impl Default for Logger {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use std::io::Cursor;
405
406 fn create_test_logger() -> (Logger, std::io::Cursor<Vec<u8>>) {
409 let cursor = Cursor::new(Vec::<u8>::new());
410 let cursor_clone = Cursor::new(Vec::<u8>::new());
411
412 let logger = Logger {
413 level: LogLevel::Info,
414 time_format: String::new(), format_strings: Logger::default_format_strings(),
416 targets: vec![RwLock::new(Box::new(cursor_clone))],
417 };
418
419 (logger, cursor)
420 }
421
422 fn create_test_logger_with_level(level: LogLevel) -> (Logger, std::io::Cursor<Vec<u8>>) {
424 let (mut logger, cursor) = create_test_logger();
425 logger.level = level;
426 (logger, cursor)
427 }
428
429 fn create_capturable_test_logger() -> (Logger, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
432 use std::io::Write;
433
434 let buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
435 let buffer_clone = buffer.clone();
436
437 struct CapturingWriter {
438 buffer: std::sync::Arc<std::sync::Mutex<Vec<u8>>>,
439 }
440
441 impl Write for CapturingWriter {
442 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
443 self.buffer.lock().unwrap().extend_from_slice(buf);
444 Ok(buf.len())
445 }
446
447 fn flush(&mut self) -> std::io::Result<()> {
448 Ok(())
449 }
450 }
451
452 let logger = Logger {
453 level: LogLevel::Info,
454 time_format: String::new(), format_strings: Logger::default_format_strings(),
456 targets: vec![RwLock::new(Box::new(CapturingWriter {
457 buffer: buffer_clone,
458 }))],
459 };
460
461 (logger, buffer)
462 }
463
464 #[test]
465 fn test_simple_logging() -> io::Result<()> {
466 let (logger, _cursor) = create_test_logger();
467
468 assert_eq!(logger.level(), LogLevel::Info);
469
470 logger.info("Hello, world!")?;
471 logger.warning("This is a warning")?;
472 logger.error("This is an error")?;
473
474 assert_eq!(logger.level(), LogLevel::Info);
475
476 Ok(())
477 }
478
479 #[test]
480 fn test_time_format() -> io::Result<()> {
481 let (mut logger, _cursor) = create_test_logger();
482 logger.time_format = "%Y-%m-%d %H:%M:%S".to_string();
483
484 assert_eq!(logger.level(), LogLevel::Info);
485
486 logger.info("Test message")?;
487
488 logger.warning("Another test message")?;
489
490 Ok(())
491 }
492
493 #[test]
494 fn test_custom_format() -> io::Result<()> {
495 let (mut logger, _cursor) = create_test_logger();
496 logger
497 .format_strings
498 .insert(LogLevel::Error, "ERROR: {message}".to_string());
499
500 assert_eq!(logger.level(), LogLevel::Info);
501
502 logger.error("Something went wrong")?;
503
504 logger.info("This should use default format")?;
505 logger.warning("This should also use default format")?;
506
507 Ok(())
508 }
509
510 #[test]
511 fn test_log_level_filtering() -> io::Result<()> {
512 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
513
514 assert_eq!(logger.level(), LogLevel::Warning);
515
516 logger.info("This should not appear")?;
517 logger.debug("This should not appear")?;
518 logger.trace("This should not appear")?;
519
520 logger.warning("This should appear")?;
521 logger.error("This should also appear")?;
522
523 assert_eq!(logger.level(), LogLevel::Warning);
524
525 Ok(())
526 }
527
528 #[test]
529 fn test_multiple_loggers() -> io::Result<()> {
530 let (logger1, _cursor1) = create_test_logger();
531 let (logger2, _cursor2) = create_test_logger_with_level(LogLevel::Warning);
532
533 assert_eq!(logger1.level(), LogLevel::Info);
534 assert_eq!(logger2.level(), LogLevel::Warning);
535
536 logger1.info("Message from logger 1")?;
537 logger2.warning("Message from logger 2")?;
538
539 logger1.info("Another message from logger 1")?;
540
541 logger2.info("This should be filtered by logger 2")?;
542
543 assert_eq!(logger1.level(), LogLevel::Info);
544 assert_eq!(logger2.level(), LogLevel::Warning);
545
546 Ok(())
547 }
548
549 #[test]
550 fn test_lazy_logging_execution() -> io::Result<()> {
551 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Info);
552
553 let mut expensive_called = false;
554
555 logger.debug_lazy(|| {
556 expensive_called = true;
557 "This should not be computed".to_string()
558 })?;
559
560 assert!(
561 !expensive_called,
562 "Expensive computation should not have been called"
563 );
564
565 expensive_called = false;
566
567 logger.info_lazy(|| {
568 expensive_called = true;
569 "This should be computed".to_string()
570 })?;
571
572 assert!(
573 expensive_called,
574 "Expensive computation should have been called"
575 );
576
577 Ok(())
578 }
579
580 #[test]
581 fn test_lazy_logging_with_expensive_computation() -> io::Result<()> {
582 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
583
584 use std::cell::RefCell;
585 let computation_count = RefCell::new(0);
586
587 logger.trace_lazy(|| {
588 *computation_count.borrow_mut() += 1;
589 "Trace message".to_string()
590 })?;
591 logger.debug_lazy(|| {
592 *computation_count.borrow_mut() += 1;
593 "Debug message".to_string()
594 })?;
595 logger.info_lazy(|| {
596 *computation_count.borrow_mut() += 1;
597 "Info message".to_string()
598 })?;
599
600 assert_eq!(
601 *computation_count.borrow(),
602 0,
603 "No expensive computations should have been executed"
604 );
605
606 logger.warning_lazy(|| {
607 *computation_count.borrow_mut() += 1;
608 "Warning message".to_string()
609 })?;
610 assert_eq!(
611 *computation_count.borrow(),
612 1,
613 "One expensive computation should have been executed"
614 );
615
616 logger.error_lazy(|| {
617 *computation_count.borrow_mut() += 1;
618 "Error message".to_string()
619 })?;
620 assert_eq!(
621 *computation_count.borrow(),
622 2,
623 "Two expensive computations should have been executed"
624 );
625
626 Ok(())
627 }
628
629 #[test]
630 fn test_lazy_logging_vs_regular_logging() -> io::Result<()> {
631 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
632 let mut lazy_called = false;
633
634 logger.warning_lazy(|| {
635 lazy_called = true;
636 "Lazy warning".to_string()
637 })?;
638
639 logger.warning("Regular warning")?;
640 assert!(lazy_called, "Lazy closure should have been called");
641
642 Ok(())
643 }
644
645 #[test]
646 fn test_multi_target_logging() -> io::Result<()> {
647 let (logger, _cursor) = create_test_logger();
648
649 logger.info("Test message for multi-target logging")?;
650
651 Ok(())
652 }
653
654 #[test]
655 fn test_multi_target_lazy_logging() -> io::Result<()> {
656 let (logger, _cursor) = create_test_logger();
657
658 let mut call_count = 0;
659
660 logger.info_lazy(|| {
661 call_count += 1;
662 "Lazy message for multiple targets".to_string()
663 })?;
664
665 assert_eq!(call_count, 1, "Lazy closure should be called only once");
666
667 Ok(())
668 }
669
670 #[test]
671 fn test_log_output_verification() -> io::Result<()> {
672 let (logger, buffer) = create_capturable_test_logger();
673
674 logger.info("Test message")?;
675
676 std::thread::sleep(std::time::Duration::from_millis(10));
677
678 let captured = buffer.lock().unwrap();
679 let output = String::from_utf8_lossy(&captured);
680
681 assert!(
682 output.contains("Test message"),
683 "Output should contain the log message"
684 );
685 assert!(
686 output.contains("[INFO]"),
687 "Output should contain the log level"
688 );
689
690 Ok(())
691 }
692
693 #[test]
694 fn test_custom_format_output_verification() -> io::Result<()> {
695 let (mut logger, buffer) = create_capturable_test_logger();
696
697 logger
698 .format_strings
699 .insert(LogLevel::Error, "ERROR: {message}".to_string());
700
701 logger.error("Something went wrong")?;
702
703 std::thread::sleep(std::time::Duration::from_millis(10));
704
705 let captured = buffer.lock().unwrap();
706 let output = String::from_utf8_lossy(&captured);
707
708 assert!(
709 output.contains("ERROR: Something went wrong"),
710 "Output should contain the custom formatted message"
711 );
712
713 Ok(())
714 }
715}