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(mut self, format: String) -> Self {
76 let levels = [
77 LogLevel::Error,
78 LogLevel::Warning,
79 LogLevel::Info,
80 LogLevel::Debug,
81 LogLevel::Trace,
82 ];
83
84 for level in levels {
85 self.format_strings.insert(level, format.clone());
86 }
87
88 self
89 }
90
91 pub fn format_for_level(mut self, level: LogLevel, format: String) -> Self {
93 self.format_strings.insert(level, format);
94 self
95 }
96
97 pub fn stdout(mut self) -> Self {
99 self.targets = vec![RwLock::new(Box::new(io::stdout()))];
100 self
101 }
102
103 pub fn stderr(mut self) -> Self {
105 self.targets = vec![RwLock::new(Box::new(io::stderr()))];
106 self
107 }
108
109 pub fn file(mut self, path: &str) -> io::Result<Self> {
111 let file = std::fs::OpenOptions::new()
112 .create(true)
113 .append(true)
114 .open(path)?;
115 self.targets = vec![RwLock::new(Box::new(file))];
116 Ok(self)
117 }
118
119 pub fn custom<W>(mut self, target: W) -> Self
139 where
140 W: Write + Send + Sync + 'static
141 {
142 self.targets = vec![RwLock::new(Box::new(target))];
143 self
144 }
145
146 pub fn add_stdout(mut self) -> Self {
148 self.targets.push(RwLock::new(Box::new(io::stdout())));
149 self
150 }
151
152 pub fn add_stderr(mut self) -> Self {
154 self.targets.push(RwLock::new(Box::new(io::stderr())));
155 self
156 }
157
158 pub fn add_file(mut self, path: &str) -> io::Result<Self> {
160 let file = std::fs::OpenOptions::new()
161 .create(true)
162 .append(true)
163 .open(path)?;
164 self.targets.push(RwLock::new(Box::new(file)));
165 Ok(self)
166 }
167
168 pub fn add_target<W>(mut self, target: W) -> Self
188 where
189 W: Write + Send + Sync + 'static
190 {
191 self.targets.push(RwLock::new(Box::new(target)));
192 self
193 }
194
195
196 pub fn log(&self, message: &str) -> io::Result<()> {
198 let formatted = self.format_message_simple(message);
199
200 for target in &self.targets {
201 let mut target = target.write().unwrap();
202 writeln!(target, "{}", formatted)?;
203 target.flush()?;
204 }
205
206 Ok(())
207 }
208
209 pub fn log_lazy<F>(&self, message_fn: F) -> io::Result<()>
231 where
232 F: FnOnce() -> String,
233 {
234 let message = message_fn();
235 self.log(&message)
236 }
237
238
239 pub(crate) fn log_lazy_with_level<F>(&self, level: LogLevel, message_fn: F) -> io::Result<()>
241 where
242 F: FnOnce() -> String,
243 {
244 if level < self.level {
245 return Ok(());
246 }
247
248 let message = message_fn();
249 self.log_with_level(level, &message)
250 }
251
252 pub(crate) fn log_with_level(&self, level: LogLevel, message: &str) -> io::Result<()> {
254 if level < self.level {
255 return Ok(());
256 }
257
258 let formatted = self.format_message(level, message);
259
260 for target in &self.targets {
261 let mut target = target.write().unwrap();
262 writeln!(target, "{}", formatted)?;
263 target.flush()?;
264 }
265
266 Ok(())
267 }
268
269 pub fn error(&self, message: &str) -> io::Result<()> {
271 self.log_with_level(LogLevel::Error, message)
272 }
273
274 pub fn warning(&self, message: &str) -> io::Result<()> {
275 self.log_with_level(LogLevel::Warning, message)
276 }
277
278 pub fn info(&self, message: &str) -> io::Result<()> {
279 self.log_with_level(LogLevel::Info, message)
280 }
281
282 pub fn debug(&self, message: &str) -> io::Result<()> {
283 self.log_with_level(LogLevel::Debug, message)
284 }
285
286 pub fn trace(&self, message: &str) -> io::Result<()> {
287 self.log_with_level(LogLevel::Trace, message)
288 }
289
290 pub fn error_lazy<F>(&self, message_fn: F) -> io::Result<()>
295 where
296 F: FnOnce() -> String,
297 {
298 self.log_lazy_with_level(LogLevel::Error, message_fn)
299 }
300
301 pub fn warning_lazy<F>(&self, message_fn: F) -> io::Result<()>
302 where
303 F: FnOnce() -> String,
304 {
305 self.log_lazy_with_level(LogLevel::Warning, message_fn)
306 }
307
308 pub fn info_lazy<F>(&self, message_fn: F) -> io::Result<()>
309 where
310 F: FnOnce() -> String,
311 {
312 self.log_lazy_with_level(LogLevel::Info, message_fn)
313 }
314
315 pub fn debug_lazy<F>(&self, message_fn: F) -> io::Result<()>
316 where
317 F: FnOnce() -> String,
318 {
319 self.log_lazy_with_level(LogLevel::Debug, message_fn)
320 }
321
322 pub fn trace_lazy<F>(&self, message_fn: F) -> io::Result<()>
323 where
324 F: FnOnce() -> String,
325 {
326 self.log_lazy_with_level(LogLevel::Trace, message_fn)
327 }
328
329 pub fn set_level(&mut self, level: LogLevel) {
331 self.level = level;
332 }
333
334 pub fn set_time_format(&mut self, format: &str) {
342 self.time_format = format.to_string();
343 }
344
345 pub fn disable_time_prefix(&mut self) {
349 self.time_format = String::new();
350 }
351
352 pub fn set_format_for_level(&mut self, level: LogLevel, format: &str) {
354 self.format_strings.insert(level, format.to_string());
355 }
356
357 pub fn level(&self) -> LogLevel {
359 self.level
360 }
361
362 pub fn get_level(&self) -> LogLevel {
364 self.level
365 }
366
367 pub fn clear_targets(mut self) -> Self {
369 self.targets.clear();
370 self
371 }
372
373 fn default_format_strings() -> HashMap<LogLevel, String> {
374 let mut formats = HashMap::new();
375 formats.insert(LogLevel::Error, "{time} [{level}] {message}".to_string());
376 formats.insert(LogLevel::Warning, "{time} [{level}] {message}".to_string());
377 formats.insert(LogLevel::Info, "{time} [{level}] {message}".to_string());
378 formats.insert(LogLevel::Debug, "{time} [{level}] {message}".to_string());
379 formats.insert(LogLevel::Trace, "{time} [{level}] {message}".to_string());
380 formats
381 }
382
383 fn format_message(&self, level: LogLevel, message: &str) -> String {
384 let format_string = self
385 .format_strings
386 .get(&level)
387 .unwrap_or(&self.format_strings[&LogLevel::Info])
388 .clone();
389
390 let time_str = if self.time_format.is_empty() {
391 String::new()
392 } else {
393 use simple_datetime_rs::{DateTime, Format};
394 DateTime::now()
395 .format(&self.time_format)
396 .unwrap_or_default()
397 };
398
399 format_string
400 .replace("{time}", &time_str)
401 .replace("{level}", &level.to_string())
402 .replace("{message}", message)
403 }
404
405 fn format_message_simple(&self, message: &str) -> String {
406 if self.time_format.is_empty() {
407 message.to_string()
408 } else {
409 use simple_datetime_rs::{DateTime, Format};
410 let time_str = DateTime::now()
411 .format(&self.time_format)
412 .unwrap_or_default();
413 format!("{} {}", time_str, message)
414 }
415 }
416}
417
418impl Default for Logger {
419 fn default() -> Self {
420 Self::new()
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427 use std::io::Cursor;
428
429 fn create_test_logger() -> (Logger, std::io::Cursor<Vec<u8>>) {
432 let cursor = Cursor::new(Vec::<u8>::new());
433 let cursor_clone = Cursor::new(Vec::<u8>::new());
434
435 let logger = Logger {
436 level: LogLevel::Info,
437 time_format: String::new(), format_strings: Logger::default_format_strings(),
439 targets: vec![RwLock::new(Box::new(cursor_clone))],
440 };
441
442 (logger, cursor)
443 }
444
445 fn create_test_logger_with_level(level: LogLevel) -> (Logger, std::io::Cursor<Vec<u8>>) {
447 let (mut logger, cursor) = create_test_logger();
448 logger.level = level;
449 (logger, cursor)
450 }
451
452 fn create_capturable_test_logger() -> (Logger, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
455 use std::io::Write;
456
457 let buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
458 let buffer_clone = buffer.clone();
459
460 struct CapturingWriter {
461 buffer: std::sync::Arc<std::sync::Mutex<Vec<u8>>>,
462 }
463
464 impl Write for CapturingWriter {
465 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
466 self.buffer.lock().unwrap().extend_from_slice(buf);
467 Ok(buf.len())
468 }
469
470 fn flush(&mut self) -> std::io::Result<()> {
471 Ok(())
472 }
473 }
474
475 let logger = Logger {
476 level: LogLevel::Info,
477 time_format: String::new(), format_strings: Logger::default_format_strings(),
479 targets: vec![RwLock::new(Box::new(CapturingWriter {
480 buffer: buffer_clone,
481 }))],
482 };
483
484 (logger, buffer)
485 }
486
487 #[test]
488 fn test_simple_logging() -> io::Result<()> {
489 let (logger, _cursor) = create_test_logger();
490
491 assert_eq!(logger.level(), LogLevel::Info);
492
493 logger.info("Hello, world!")?;
494 logger.warning("This is a warning")?;
495 logger.error("This is an error")?;
496
497 assert_eq!(logger.level(), LogLevel::Info);
498
499 Ok(())
500 }
501
502 #[test]
503 fn test_time_format() -> io::Result<()> {
504 let (mut logger, _cursor) = create_test_logger();
505 logger.time_format = "%Y-%m-%d %H:%M:%S".to_string();
506
507 assert_eq!(logger.level(), LogLevel::Info);
508
509 logger.info("Test message")?;
510
511 logger.warning("Another test message")?;
512
513 Ok(())
514 }
515
516 #[test]
517 fn test_custom_format() -> io::Result<()> {
518 let (mut logger, _cursor) = create_test_logger();
519 logger
520 .format_strings
521 .insert(LogLevel::Error, "ERROR: {message}".to_string());
522
523 assert_eq!(logger.level(), LogLevel::Info);
524
525 logger.error("Something went wrong")?;
526
527 logger.info("This should use default format")?;
528 logger.warning("This should also use default format")?;
529
530 Ok(())
531 }
532
533 #[test]
534 fn test_log_level_filtering() -> io::Result<()> {
535 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
536
537 assert_eq!(logger.level(), LogLevel::Warning);
538
539 logger.info("This should not appear")?;
540 logger.debug("This should not appear")?;
541 logger.trace("This should not appear")?;
542
543 logger.warning("This should appear")?;
544 logger.error("This should also appear")?;
545
546 assert_eq!(logger.level(), LogLevel::Warning);
547
548 Ok(())
549 }
550
551 #[test]
552 fn test_multiple_loggers() -> io::Result<()> {
553 let (logger1, _cursor1) = create_test_logger();
554 let (logger2, _cursor2) = create_test_logger_with_level(LogLevel::Warning);
555
556 assert_eq!(logger1.level(), LogLevel::Info);
557 assert_eq!(logger2.level(), LogLevel::Warning);
558
559 logger1.info("Message from logger 1")?;
560 logger2.warning("Message from logger 2")?;
561
562 logger1.info("Another message from logger 1")?;
563
564 logger2.info("This should be filtered by logger 2")?;
565
566 assert_eq!(logger1.level(), LogLevel::Info);
567 assert_eq!(logger2.level(), LogLevel::Warning);
568
569 Ok(())
570 }
571
572 #[test]
573 fn test_lazy_logging_execution() -> io::Result<()> {
574 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Info);
575
576 let mut expensive_called = false;
577
578 logger.debug_lazy(|| {
579 expensive_called = true;
580 "This should not be computed".to_string()
581 })?;
582
583 assert!(
584 !expensive_called,
585 "Expensive computation should not have been called"
586 );
587
588 expensive_called = false;
589
590 logger.info_lazy(|| {
591 expensive_called = true;
592 "This should be computed".to_string()
593 })?;
594
595 assert!(
596 expensive_called,
597 "Expensive computation should have been called"
598 );
599
600 Ok(())
601 }
602
603 #[test]
604 fn test_lazy_logging_with_expensive_computation() -> io::Result<()> {
605 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
606
607 use std::cell::RefCell;
608 let computation_count = RefCell::new(0);
609
610 logger.trace_lazy(|| {
611 *computation_count.borrow_mut() += 1;
612 "Trace message".to_string()
613 })?;
614 logger.debug_lazy(|| {
615 *computation_count.borrow_mut() += 1;
616 "Debug message".to_string()
617 })?;
618 logger.info_lazy(|| {
619 *computation_count.borrow_mut() += 1;
620 "Info message".to_string()
621 })?;
622
623 assert_eq!(
624 *computation_count.borrow(),
625 0,
626 "No expensive computations should have been executed"
627 );
628
629 logger.warning_lazy(|| {
630 *computation_count.borrow_mut() += 1;
631 "Warning message".to_string()
632 })?;
633 assert_eq!(
634 *computation_count.borrow(),
635 1,
636 "One expensive computation should have been executed"
637 );
638
639 logger.error_lazy(|| {
640 *computation_count.borrow_mut() += 1;
641 "Error message".to_string()
642 })?;
643 assert_eq!(
644 *computation_count.borrow(),
645 2,
646 "Two expensive computations should have been executed"
647 );
648
649 Ok(())
650 }
651
652 #[test]
653 fn test_lazy_logging_vs_regular_logging() -> io::Result<()> {
654 let (logger, _cursor) = create_test_logger_with_level(LogLevel::Warning);
655 let mut lazy_called = false;
656
657 logger.warning_lazy(|| {
658 lazy_called = true;
659 "Lazy warning".to_string()
660 })?;
661
662 logger.warning("Regular warning")?;
663 assert!(lazy_called, "Lazy closure should have been called");
664
665 Ok(())
666 }
667
668 #[test]
669 fn test_multi_target_logging() -> io::Result<()> {
670 let (logger, _cursor) = create_test_logger();
671
672 logger.info("Test message for multi-target logging")?;
673
674 Ok(())
675 }
676
677 #[test]
678 fn test_multi_target_lazy_logging() -> io::Result<()> {
679 let (logger, _cursor) = create_test_logger();
680
681 let mut call_count = 0;
682
683 logger.info_lazy(|| {
684 call_count += 1;
685 "Lazy message for multiple targets".to_string()
686 })?;
687
688 assert_eq!(call_count, 1, "Lazy closure should be called only once");
689
690 Ok(())
691 }
692
693 #[test]
694 fn test_log_output_verification() -> io::Result<()> {
695 let (logger, buffer) = create_capturable_test_logger();
696
697 logger.info("Test message")?;
698
699 std::thread::sleep(std::time::Duration::from_millis(10));
700
701 let captured = buffer.lock().unwrap();
702 let output = String::from_utf8_lossy(&captured);
703
704 assert!(
705 output.contains("Test message"),
706 "Output should contain the log message"
707 );
708 assert!(
709 output.contains("[INFO]"),
710 "Output should contain the log level"
711 );
712
713 Ok(())
714 }
715
716 #[test]
717 fn test_custom_format_output_verification() -> io::Result<()> {
718 let (mut logger, buffer) = create_capturable_test_logger();
719
720 logger
721 .format_strings
722 .insert(LogLevel::Error, "ERROR: {message}".to_string());
723
724 logger.error("Something went wrong")?;
725
726 std::thread::sleep(std::time::Duration::from_millis(10));
727
728 let captured = buffer.lock().unwrap();
729 let output = String::from_utf8_lossy(&captured);
730
731 assert!(
732 output.contains("ERROR: Something went wrong"),
733 "Output should contain the custom formatted message"
734 );
735
736 Ok(())
737 }
738
739 #[test]
740 fn test_format_sets_same_format_for_all_levels_output() -> io::Result<()> {
741 const LOG_PREFIX: &str = "worker-1";
742
743 let (logger, buffer) = create_capturable_test_logger();
744 let logger = logger.format(format!("[{{level}}][{}] {{message}}", LOG_PREFIX));
745
746 logger.error("Error message")?;
747 logger.info("Info message")?;
748
749 std::thread::sleep(std::time::Duration::from_millis(10));
750
751 let captured = buffer.lock().unwrap();
752 let output = String::from_utf8_lossy(&captured);
753
754 assert!(
755 output.contains("[ERROR][worker-1] Error message"),
756 "Output should contain formatted error message with prefix"
757 );
758 assert!(
759 output.contains("[INFO][worker-1] Info message"),
760 "Output should contain formatted info message with prefix"
761 );
762
763 Ok(())
764 }
765}