Skip to main content

fb2_clean/cli/
logger.rs

1use log::{Level, LevelFilter, Log, Metadata, Record};
2use std::{
3    env,
4    io::{self, IsTerminal, Write, stderr, stdout},
5    sync::LazyLock,
6};
7
8static CLI_LOGGER: CliLogger = CliLogger;
9pub struct CliLogger;
10
11static STDOUT_ON_COLOR: LazyLock<bool> =
12    LazyLock::new(|| should_enable_color(stdout().is_terminal()));
13static STDERR_ON_COLOR: LazyLock<bool> =
14    LazyLock::new(|| should_enable_color(stderr().is_terminal()));
15
16impl CliLogger {
17    pub fn init() {
18        log::set_logger(&CLI_LOGGER).unwrap();
19        log::set_max_level(LevelFilter::Info);
20    }
21
22    fn prf_prefix(level: Level) -> &'static str {
23        match level {
24            Level::Error if *STDERR_ON_COLOR => "\x1b[31m",
25            Level::Error => "",
26            Level::Warn if *STDERR_ON_COLOR => "\x1b[33m",
27            Level::Warn => "",
28            Level::Debug if *STDOUT_ON_COLOR => "\x1b[34m",
29            Level::Debug => "",
30            Level::Trace if *STDOUT_ON_COLOR => "\x1b[35m",
31            Level::Trace => "",
32            _ => "",
33        }
34    }
35
36    fn prf_suffix(level: Level) -> &'static str {
37        match level {
38            Level::Error | Level::Warn if *STDERR_ON_COLOR => "\x1b[0m: ",
39            Level::Error | Level::Warn => ": ",
40            Level::Debug | Level::Trace if *STDOUT_ON_COLOR => "\x1b[0m: ",
41            Level::Debug | Level::Trace => ": ",
42            _ => "",
43        }
44    }
45}
46
47impl Log for CliLogger {
48    fn enabled(&self, metadata: &Metadata) -> bool {
49        metadata.target().starts_with("fb2_clean")
50    }
51
52    fn log(&self, record: &Record) {
53        if !self.enabled(record.metadata()) {
54            return;
55        }
56
57        let level = record.level();
58
59        let msg = format!(
60            "{}{}{}{}\n",
61            Self::prf_prefix(level),
62            crate::log_prefix_root(level),
63            Self::prf_suffix(level),
64            record.args()
65        );
66        let msg = msg.as_bytes();
67
68        match level {
69            Level::Error | Level::Warn => {
70                let _ = io::stderr()
71                    .write_all(msg)
72                    .or_else(|_| io::stdout().write_all(msg));
73            }
74            _ => {
75                let _ = io::stdout().write_all(msg);
76            }
77        }
78    }
79
80    fn flush(&self) {}
81}
82
83fn should_enable_color(stream_is_terminal: bool) -> bool {
84    if !stream_is_terminal {
85        return false;
86    }
87
88    if env::var_os("NO_COLOR").is_some() {
89        return false;
90    }
91    if env::var("TERM").unwrap_or_default() == "dumb" {
92        return false;
93    }
94
95    #[cfg(windows)]
96    {
97        enable_ansi_support::enable_ansi_support().is_ok()
98    }
99
100    #[cfg(unix)]
101    {
102        true
103    }
104}