1use chrono::Local;
2use log::{Level, LevelFilter, Log, Metadata, Record};
3
4use super::{FormatArgs, translate_args};
5
6#[derive(Debug, Clone, Copy)]
7pub enum Console {
8 Stdout,
9 Stderr,
10}
11
12pub struct ConsoleLogger {
13 level: LevelFilter,
14 console: Console,
15 color: bool,
16 target_filter: Box<dyn Fn(&str) -> bool + Send + Sync>,
17 formatter: Box<dyn Fn(FormatArgs) -> String + Send + Sync>,
18}
19
20impl ConsoleLogger {
21 pub fn new_boxed<F1, F2>(
22 console: Console,
23 level: LevelFilter,
24 target_filter: F1,
25 formatter: F2,
26 ) -> Box<dyn Log>
27 where
28 F1: Fn(&str) -> bool + Send + Sync + 'static,
29 F2: Fn(FormatArgs) -> String + Send + Sync + 'static,
30 {
31 let color = is_console(console);
32 Box::new(Self {
33 level,
34 console,
35 color,
36 target_filter: Box::new(target_filter),
37 formatter: Box::new(formatter),
38 })
39 }
40}
41
42impl Log for ConsoleLogger {
43 fn enabled(&self, metadata: &Metadata) -> bool {
44 metadata.level() <= self.level && (self.target_filter)(metadata.target())
45 }
46
47 fn log(&self, record: &Record) {
48 if !self.enabled(record.metadata()) {
49 return;
50 }
51
52 const COL_RED: &str = "\x1b[31m";
53 const COL_YELLOW: &str = "\x1b[33m";
54 const COL_GREEN: &str = "\x1b[32m";
55 const COL_PURPLE: &str = "\x1b[35m";
56 const COL_RESET: &str = "\x1b[0m";
57
58 let timestamp = Local::now();
59
60 let level = record.level();
61 let level_str = if self.color {
62 match level {
63 Level::Error => format!("{COL_RED}[{level:5}]{COL_RESET}"),
64 Level::Warn => format!("{COL_YELLOW}[{level:5}]{COL_RESET}"),
65 Level::Info => format!("{COL_GREEN}[{level:5}]{COL_RESET}"),
66 Level::Debug => format!("{COL_PURPLE}[{level:5}]{COL_RESET}"),
67 _ => format!("[{level:5}]"),
68 }
69 } else {
70 format!("[{level:5}]")
71 };
72 let mut args = translate_args(record, timestamp);
73 args.level_str = level_str;
74
75 let output = self.formatter.as_ref()(args);
76 match self.console {
77 Console::Stdout => {
78 println!("{output}");
79 }
80 Console::Stderr => {
81 eprintln!("{output}");
82 }
83 }
84 }
85
86 fn flush(&self) {}
87}
88
89fn is_console(console: Console) -> bool {
93 let fd = match console {
94 Console::Stdout => libc::STDOUT_FILENO,
95 Console::Stderr => libc::STDERR_FILENO,
96 };
97 let ret = unsafe { libc::isatty(fd) };
98
99 ret != 0
100}