1use log::{Level, LevelFilter, Log, Metadata, Record};
2use std::{
3 io::{self, Write},
4 sync::LazyLock,
5};
6use supports_color::{Stream, on};
7
8static CLI_LOGGER: CliLogger = CliLogger;
9pub struct CliLogger;
10
11static STDERR_ON_COLOR: LazyLock<bool> = LazyLock::new(|| on(Stream::Stderr).is_some());
12static STDOUT_ON_COLOR: LazyLock<bool> = LazyLock::new(|| on(Stream::Stdout).is_some());
13
14impl CliLogger {
15 pub fn init() {
16 log::set_logger(&CLI_LOGGER).unwrap();
17 log::set_max_level(LevelFilter::Info);
18 }
19
20 fn prf_prefix(level: Level) -> &'static str {
21 match level {
22 Level::Error if *STDERR_ON_COLOR => "\x1b[31m",
23 Level::Error => "",
24 Level::Warn if *STDERR_ON_COLOR => "\x1b[33m",
25 Level::Warn => "",
26 Level::Debug if *STDOUT_ON_COLOR => "\x1b[34m",
27 Level::Debug => "",
28 Level::Trace if *STDOUT_ON_COLOR => "\x1b[35m",
29 Level::Trace => "",
30 _ => "",
31 }
32 }
33
34 fn prf_suffix(level: Level) -> &'static str {
35 match level {
36 Level::Error | Level::Warn if *STDERR_ON_COLOR => "\x1b[0m: ",
37 Level::Error | Level::Warn => ": ",
38 Level::Debug | Level::Trace if *STDOUT_ON_COLOR => "\x1b[0m: ",
39 Level::Debug | Level::Trace => ": ",
40 _ => "",
41 }
42 }
43}
44
45impl Log for CliLogger {
46 fn enabled(&self, metadata: &Metadata) -> bool {
47 metadata.level() <= Level::Trace
48 }
49
50 fn log(&self, record: &Record) {
51 if !self.enabled(record.metadata()) {
52 return;
53 }
54
55 let level = record.level();
56
57 let msg = format!(
58 "{}{}{}{}\n",
59 Self::prf_prefix(level),
60 crate::log_prefix_root(level),
61 Self::prf_suffix(level),
62 record.args()
63 );
64 let msg = msg.as_bytes();
65
66 match level {
67 Level::Error | Level::Warn => {
68 let _ = io::stderr()
69 .write_all(msg)
70 .or_else(|_| io::stdout().write_all(msg));
71 }
72 _ => {
73 let _ = io::stdout().write_all(msg);
74 }
75 }
76 }
77
78 fn flush(&self) {}
79}