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}