1pub 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
15pub const REBALANCE_LOG_LEVEL: log::Level = log::Level::Trace;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub struct LogFilter {
22 pub default: log::LevelFilter,
24 pub pathing: log::LevelFilter,
26}
27
28pub 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
51pub 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}