Skip to main content

yt/
logger.rs

1use std::io::Write as _;
2
3use log::{Level, LevelFilter, Log, Metadata, Record};
4
5use crate::env::Env;
6
7pub fn level(verbose: u8, quiet: bool, env: &Env) -> LevelFilter {
8    if let Some(level) = env
9        .get("YT_LOG")
10        .and_then(|value| value.parse::<LevelFilter>().ok())
11    {
12        return level;
13    }
14    match (quiet, verbose) {
15        (true, _) => LevelFilter::Error,
16        (false, 0) => LevelFilter::Warn,
17        (false, 1) => LevelFilter::Debug,
18        (false, _) => LevelFilter::Trace,
19    }
20}
21
22pub fn init(level: LevelFilter) {
23    if log::set_boxed_logger(Box::new(StderrLogger { level })).is_ok() {
24        log::set_max_level(level);
25    }
26}
27
28struct StderrLogger {
29    level: LevelFilter,
30}
31
32impl Log for StderrLogger {
33    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
34        metadata.level() <= self.level && metadata.target().starts_with("yt")
35    }
36
37    fn log(&self, record: &Record<'_>) {
38        if self.enabled(record.metadata()) {
39            let _ = writeln!(
40                std::io::stderr().lock(),
41                "{}",
42                line(record.level(), &record.args().to_string())
43            );
44        }
45    }
46
47    fn flush(&self) {}
48}
49
50fn line(level: Level, message: &str) -> String {
51    let label = match level {
52        Level::Error => "error",
53        Level::Warn => "warning",
54        Level::Info => "info",
55        Level::Debug => "debug",
56        Level::Trace => "trace",
57    };
58    format!(
59        "yt {label}: {}",
60        crate::output::printable(&crate::redact::redact(message))
61    )
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn flags_map_to_levels() {
70        let env = Env::default();
71        assert_eq!(level(0, false, &env), LevelFilter::Warn);
72        assert_eq!(level(1, false, &env), LevelFilter::Debug);
73        assert_eq!(level(2, false, &env), LevelFilter::Trace);
74        assert_eq!(level(0, true, &env), LevelFilter::Error);
75    }
76
77    #[test]
78    fn yt_log_overrides_flags() {
79        let env = Env::from_vars([("YT_LOG", "info")]);
80        assert_eq!(level(2, false, &env), LevelFilter::Info);
81    }
82
83    #[test]
84    fn lines_are_printable() {
85        assert_eq!(line(Level::Info, "a\u{1b}[2Jb"), "yt info: a\u{fffd}[2Jb");
86    }
87
88    #[test]
89    fn lines_are_redacted() {
90        crate::redact::register("perm-bG9nZ2Vy.dGVzdA==.zzzz9999yyyy");
91        let line = line(Level::Debug, "token perm-bG9nZ2Vy.dGVzdA==.zzzz9999yyyy");
92        assert_eq!(line, "yt debug: token [REDACTED]");
93    }
94}