Skip to main content

rlmctl_core/
logging.rs

1//! Shared tracing setup for the CLI, GUI, and guard daemon: logs go to
2//! stderr (so stdout stays script-clean), colored only when stderr is a
3//! terminal, filtered by `RUST_LOG` when set, else by a per-binary default.
4
5use std::io::IsTerminal;
6use tracing_subscriber::EnvFilter;
7
8/// The env-filter directive string: `env` when it holds a non-blank value,
9/// else `default_level` lowercased (e.g. "warn"). A blank `RUST_LOG=""` is
10/// treated the same as unset, rather than as an empty (match-nothing) filter.
11pub fn filter_spec(env: Option<&str>, default_level: tracing::Level) -> String {
12    match env {
13        Some(v) if !v.is_empty() => v.to_string(),
14        _ => default_level.as_str().to_lowercase(),
15    }
16}
17
18/// Install the global tracing subscriber. Writes to stderr, with ANSI colors
19/// only when stderr is a terminal, filtered by `RUST_LOG` (or
20/// `default_level` when it is unset or blank). Safe to call more than once;
21/// later calls are silently ignored.
22pub fn init(default_level: tracing::Level) {
23    let spec = filter_spec(std::env::var("RUST_LOG").ok().as_deref(), default_level);
24    let _ = tracing_subscriber::fmt()
25        .with_env_filter(EnvFilter::new(spec))
26        .with_writer(std::io::stderr)
27        .with_ansi(std::io::stderr().is_terminal())
28        .try_init();
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn env_overrides_default_and_blank_env_is_ignored() {
37        assert_eq!(filter_spec(None, tracing::Level::WARN), "warn");
38        assert_eq!(filter_spec(Some(""), tracing::Level::INFO), "info");
39        assert_eq!(
40            filter_spec(Some("rlm=debug"), tracing::Level::WARN),
41            "rlm=debug"
42        );
43    }
44}