Skip to main content

dig_logging/
filter.rs

1//! Level-filter resolution (SPEC §5).
2//!
3//! One `EnvFilter` drives both sinks. Its directive is chosen by a fixed precedence — a persisted
4//! operator choice, then the ecosystem-common `DIG_LOG`, then the Rust-conventional `RUST_LOG`, then
5//! a noise-trimmed default. The choice is a PURE function of its four inputs so the precedence is
6//! table-testable without the process environment.
7
8/// The env var carrying an operator-set filter (ecosystem-common name), checked before `RUST_LOG`.
9pub const ENV_DIG_LOG: &str = "DIG_LOG";
10
11/// The Rust-conventional filter env var, kept working as the lowest-priority env source.
12pub const ENV_RUST_LOG: &str = "RUST_LOG";
13
14/// The baked-in default: `info`, with the chattiest transport crates trimmed to `warn`.
15pub const DEFAULT_DIRECTIVE: &str = "info,hyper=warn,rustls=warn,h2=warn,tower=warn";
16
17/// Resolve the effective filter directive by precedence (SPEC §5): the first non-empty of
18/// `persisted` > `dig_log` > `rust_log`, else [`DEFAULT_DIRECTIVE`]. Whitespace-only inputs count as
19/// empty (an exported-but-blank env var must not silence logging).
20pub fn resolve_filter(
21    persisted: Option<&str>,
22    dig_log: Option<&str>,
23    rust_log: Option<&str>,
24) -> String {
25    [persisted, dig_log, rust_log]
26        .into_iter()
27        .flatten()
28        .map(str::trim)
29        .find(|value| !value.is_empty())
30        .unwrap_or(DEFAULT_DIRECTIVE)
31        .to_string()
32}
33
34/// Resolve the directive from the real environment given an optional persisted choice.
35pub fn resolve_filter_from_env(persisted: Option<&str>) -> String {
36    let dig_log = std::env::var(ENV_DIG_LOG).ok();
37    let rust_log = std::env::var(ENV_RUST_LOG).ok();
38    resolve_filter(persisted, dig_log.as_deref(), rust_log.as_deref())
39}
40
41/// The file (inside the service log dir) an operator's persisted `logs level` choice is stored in.
42pub const LEVEL_FILE: &str = "level";
43
44/// Read the persisted level directive from a service log `dir`, if any (`None` when absent/blank).
45pub fn read_persisted_level(dir: &std::path::Path) -> Option<String> {
46    std::fs::read_to_string(dir.join(LEVEL_FILE))
47        .ok()
48        .map(|value| value.trim().to_string())
49        .filter(|value| !value.is_empty())
50}
51
52/// Persist an operator's level directive into a service log `dir` (SPEC §5 / §8.1 `logs level`).
53pub fn write_persisted_level(dir: &std::path::Path, directive: &str) -> std::io::Result<()> {
54    std::fs::create_dir_all(dir)?;
55    std::fs::write(dir.join(LEVEL_FILE), directive.trim())
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn persisted_beats_everything() {
64        assert_eq!(
65            resolve_filter(Some("debug"), Some("trace"), Some("warn")),
66            "debug"
67        );
68    }
69
70    #[test]
71    fn dig_log_beats_rust_log() {
72        assert_eq!(resolve_filter(None, Some("trace"), Some("warn")), "trace");
73    }
74
75    #[test]
76    fn rust_log_used_when_only_source() {
77        assert_eq!(resolve_filter(None, None, Some("error")), "error");
78    }
79
80    #[test]
81    fn default_when_all_absent_or_blank() {
82        assert_eq!(resolve_filter(None, None, None), DEFAULT_DIRECTIVE);
83        assert_eq!(
84            resolve_filter(Some("  "), Some(""), None),
85            DEFAULT_DIRECTIVE
86        );
87    }
88
89    #[test]
90    fn persisted_level_round_trips_and_blank_is_none() {
91        let dir = tempfile::tempdir().unwrap();
92        assert_eq!(read_persisted_level(dir.path()), None);
93        write_persisted_level(dir.path(), "  debug,h2=warn  ").unwrap();
94        assert_eq!(
95            read_persisted_level(dir.path()),
96            Some("debug,h2=warn".to_string())
97        );
98        write_persisted_level(dir.path(), "   ").unwrap();
99        assert_eq!(
100            read_persisted_level(dir.path()),
101            None,
102            "blank persisted level reads as unset"
103        );
104    }
105}