cpp_linter/
logger.rs

1//! A module to initialize and customize the logger object used in (most) stdout.
2
3use std::{
4    env,
5    io::{stdout, Write},
6};
7
8use colored::{control::set_override, Colorize};
9use log::{Level, LevelFilter, Log, Metadata, Record};
10
11#[derive(Default)]
12struct SimpleLogger;
13
14impl SimpleLogger {
15    fn level_color(level: &Level) -> String {
16        let name = format!("{:>5}", level.as_str().to_uppercase());
17        match level {
18            Level::Error => name.red().bold().to_string(),
19            Level::Warn => name.yellow().bold().to_string(),
20            Level::Info => name.green().bold().to_string(),
21            Level::Debug => name.blue().bold().to_string(),
22            Level::Trace => name.magenta().bold().to_string(),
23        }
24    }
25}
26
27impl Log for SimpleLogger {
28    fn enabled(&self, metadata: &Metadata) -> bool {
29        metadata.level() <= log::max_level()
30    }
31
32    fn log(&self, record: &Record) {
33        let mut stdout = stdout().lock();
34        if record.target() == "CI_LOG_GROUPING" {
35            // this log is meant to manipulate a CI workflow's log grouping
36            writeln!(stdout, "{}", record.args()).expect("Failed to write log command to stdout");
37            stdout
38                .flush()
39                .expect("Failed to flush log command to stdout");
40        } else if self.enabled(record.metadata()) {
41            let module = record.module_path();
42            if module.is_none_or(|v| v.starts_with("cpp_linter")) {
43                writeln!(
44                    stdout,
45                    "[{}]: {}",
46                    Self::level_color(&record.level()),
47                    record.args()
48                )
49                .expect("Failed to write log message to stdout");
50            } else {
51                writeln!(
52                    stdout,
53                    "[{}]{{{}:{}}}: {}",
54                    Self::level_color(&record.level()),
55                    module.unwrap(), // safe to unwrap here because the None case is caught above
56                    record.line().unwrap_or_default(),
57                    record.args()
58                )
59                .expect("Failed to write detailed log message to stdout");
60            }
61            stdout
62                .flush()
63                .expect("Failed to flush log message to stdout");
64        }
65    }
66
67    fn flush(&self) {}
68}
69
70/// A function to initialize the private `LOGGER`.
71///
72/// The logging level defaults to [`LevelFilter::Info`].
73/// This logs a debug message about [`SetLoggerError`](struct@log::SetLoggerError)
74/// if the `LOGGER` is already initialized.
75pub fn try_init() {
76    let logger: SimpleLogger = SimpleLogger;
77    if matches!(
78        env::var("CPP_LINTER_COLOR").as_deref(),
79        Ok("on" | "1" | "true")
80    ) {
81        set_override(true);
82    }
83    if let Err(e) =
84        log::set_boxed_logger(Box::new(logger)).map(|()| log::set_max_level(LevelFilter::Info))
85    {
86        // logger singleton already instantiated.
87        // we'll just use whatever the current config is.
88        log::debug!("{e:?}");
89    }
90}
91
92#[cfg(test)]
93mod test {
94    use std::env;
95
96    use super::{try_init, SimpleLogger};
97
98    #[test]
99    fn trace_log() {
100        env::set_var("CPP_LINTER_COLOR", "true");
101        try_init();
102        assert!(SimpleLogger::level_color(&log::Level::Trace).contains("TRACE"));
103        log::set_max_level(log::LevelFilter::Trace);
104        log::trace!("A dummy log statement for code coverage");
105    }
106}