Skip to main content

rns_net/
logging.rs

1//! Reticulum-compatible numeric logging levels and target filters.
2
3pub use rns_core::logging::PATHING_LOG_TARGET;
4
5pub const LOG_CRITICAL: u8 = 0;
6pub const LOG_ERROR: u8 = 1;
7pub const LOG_WARNING: u8 = 2;
8pub const LOG_NOTICE: u8 = 3;
9pub const LOG_INFO: u8 = 4;
10pub const LOG_VERBOSE: u8 = 5;
11pub const LOG_DEBUG: u8 = 6;
12pub const LOG_PATHING: u8 = 7;
13pub const LOG_EXTREME: u8 = 8;
14
15/// Runtime level for authenticated link path rebalancing events.
16pub const REBALANCE_LOG_LEVEL: log::Level = log::Level::Trace;
17
18/// The two filters needed to represent Reticulum's pathing level with Rust's
19/// five-level `log` facade.
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub struct LogFilter {
22    /// Filter applied to every ordinary logging target.
23    pub default: log::LevelFilter,
24    /// Filter applied specifically to [`PATHING_LOG_TARGET`].
25    pub pathing: log::LevelFilter,
26}
27
28/// Translate a Reticulum numeric level into default and pathing filters.
29///
30/// Values above the supported range are clamped defensively. Configuration
31/// parsing rejects them, but callers can also supply levels programmatically.
32pub fn numeric_log_filter(log_level: u8) -> LogFilter {
33    let log_level = log_level.min(LOG_EXTREME);
34    let default = match log_level {
35        LOG_CRITICAL | LOG_ERROR => log::LevelFilter::Error,
36        LOG_WARNING => log::LevelFilter::Warn,
37        LOG_NOTICE | LOG_INFO => log::LevelFilter::Info,
38        LOG_VERBOSE | LOG_DEBUG | LOG_PATHING => log::LevelFilter::Debug,
39        LOG_EXTREME => log::LevelFilter::Trace,
40        _ => unreachable!("numeric log level was clamped"),
41    };
42    let pathing = if log_level >= LOG_PATHING {
43        log::LevelFilter::Trace
44    } else {
45        default
46    };
47
48    LogFilter { default, pathing }
49}
50
51/// Apply foreground CLI verbosity and quietness to a configured numeric level.
52pub fn adjust_log_level(configured: u8, verbosity: u8, quietness: u8) -> u8 {
53    i32::from(configured)
54        .saturating_add(i32::from(verbosity))
55        .saturating_sub(i32::from(quietness))
56        .clamp(i32::from(LOG_CRITICAL), i32::from(LOG_EXTREME)) as u8
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use log::Log;
63
64    fn test_logger(filter: LogFilter) -> env_logger::Logger {
65        let mut builder = env_logger::Builder::new();
66        builder
67            .filter_level(filter.default)
68            .filter_module(PATHING_LOG_TARGET, filter.pathing);
69        builder.build()
70    }
71
72    #[test]
73    fn numeric_levels_preserve_reticulum_severity_bands() {
74        let expected = [
75            log::LevelFilter::Error,
76            log::LevelFilter::Error,
77            log::LevelFilter::Warn,
78            log::LevelFilter::Info,
79            log::LevelFilter::Info,
80            log::LevelFilter::Debug,
81            log::LevelFilter::Debug,
82            log::LevelFilter::Debug,
83            log::LevelFilter::Trace,
84        ];
85
86        for (level, default) in expected.into_iter().enumerate() {
87            let filter = numeric_log_filter(level as u8);
88            assert_eq!(filter.default, default, "numeric level {level}");
89        }
90    }
91
92    #[test]
93    fn pathing_is_hidden_at_debug_and_enabled_at_pathing() {
94        let debug = numeric_log_filter(LOG_DEBUG);
95        assert_eq!(debug.default, log::LevelFilter::Debug);
96        assert_eq!(debug.pathing, log::LevelFilter::Debug);
97
98        let pathing = numeric_log_filter(LOG_PATHING);
99        assert_eq!(pathing.default, log::LevelFilter::Debug);
100        assert_eq!(pathing.pathing, log::LevelFilter::Trace);
101    }
102
103    #[test]
104    fn extreme_enables_all_trace_targets() {
105        let filter = numeric_log_filter(LOG_EXTREME);
106        assert_eq!(filter.default, log::LevelFilter::Trace);
107        assert_eq!(filter.pathing, log::LevelFilter::Trace);
108    }
109
110    #[test]
111    fn level_seven_env_filter_enables_only_pathing_trace() {
112        let logger = test_logger(numeric_log_filter(LOG_PATHING));
113        let pathing = log::Metadata::builder()
114            .level(log::Level::Trace)
115            .target(PATHING_LOG_TARGET)
116            .build();
117        let ordinary_trace = log::Metadata::builder()
118            .level(log::Level::Trace)
119            .target("rns_net::driver")
120            .build();
121        let ordinary_debug = log::Metadata::builder()
122            .level(log::Level::Debug)
123            .target("rns_net::driver")
124            .build();
125
126        assert!(logger.enabled(&pathing));
127        assert!(!logger.enabled(&ordinary_trace));
128        assert!(logger.enabled(&ordinary_debug));
129    }
130
131    #[test]
132    fn level_six_hides_pathing_trace_and_level_eight_enables_all_trace() {
133        let pathing = log::Metadata::builder()
134            .level(log::Level::Trace)
135            .target(PATHING_LOG_TARGET)
136            .build();
137        let ordinary_trace = log::Metadata::builder()
138            .level(log::Level::Trace)
139            .target("rns_core::resource")
140            .build();
141
142        let debug = test_logger(numeric_log_filter(LOG_DEBUG));
143        assert!(!debug.enabled(&pathing));
144        assert!(!debug.enabled(&ordinary_trace));
145
146        let extreme = test_logger(numeric_log_filter(LOG_EXTREME));
147        assert!(extreme.enabled(&pathing));
148        assert!(extreme.enabled(&ordinary_trace));
149    }
150
151    #[test]
152    fn link_path_rebalancing_uses_pathing_level() {
153        assert_eq!(REBALANCE_LOG_LEVEL, log::Level::Trace);
154        let metadata = log::Metadata::builder()
155            .level(REBALANCE_LOG_LEVEL)
156            .target(PATHING_LOG_TARGET)
157            .build();
158
159        let debug = test_logger(numeric_log_filter(LOG_DEBUG));
160        assert!(!debug.enabled(&metadata));
161
162        let pathing = test_logger(numeric_log_filter(LOG_PATHING));
163        assert!(pathing.enabled(&metadata));
164    }
165
166    #[test]
167    fn out_of_range_numeric_levels_are_safely_clamped() {
168        assert_eq!(numeric_log_filter(u8::MAX), numeric_log_filter(LOG_EXTREME));
169    }
170
171    #[test]
172    fn cli_adjustment_is_signed_and_clamped_to_supported_range() {
173        assert_eq!(adjust_log_level(4, 2, 0), 6);
174        assert_eq!(adjust_log_level(4, 0, 2), 2);
175        assert_eq!(adjust_log_level(4, 3, 1), 6);
176        assert_eq!(adjust_log_level(7, 2, 0), LOG_EXTREME);
177        assert_eq!(adjust_log_level(1, 0, 2), LOG_CRITICAL);
178        assert_eq!(adjust_log_level(4, u8::MAX, 0), LOG_EXTREME);
179        assert_eq!(adjust_log_level(4, 0, u8::MAX), LOG_CRITICAL);
180    }
181}