ant_quic/logging/
filters.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8/// Log filtering capabilities
9///
10/// Provides flexible filtering of log messages by component, level, and other criteria
11use std::collections::HashMap;
12use tracing::Level;
13
14/// Log filter configuration
15#[derive(Debug, Clone)]
16pub struct LogFilter {
17    /// Component-specific log levels
18    component_levels: HashMap<String, Level>,
19    /// Default log level
20    default_level: Level,
21    /// Regex patterns to exclude
22    exclude_patterns: Vec<regex::Regex>,
23    /// Regex patterns to include (overrides excludes)
24    include_patterns: Vec<regex::Regex>,
25}
26
27impl LogFilter {
28    /// Create a new log filter with default settings
29    pub fn new() -> Self {
30        Self {
31            component_levels: HashMap::new(),
32            default_level: Level::INFO,
33            exclude_patterns: Vec::new(),
34            include_patterns: Vec::new(),
35        }
36    }
37
38    /// Set the default log level
39    pub fn with_default_level(mut self, level: Level) -> Self {
40        self.default_level = level;
41        self
42    }
43
44    /// Set log level for a specific module/component
45    pub fn with_module(mut self, module: &str, level: Level) -> Self {
46        self.component_levels.insert(module.to_string(), level);
47        self
48    }
49
50    /// Add an exclude pattern
51    pub fn exclude_pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
52        let regex = regex::Regex::new(pattern)?;
53        self.exclude_patterns.push(regex);
54        Ok(self)
55    }
56
57    /// Add an include pattern (overrides excludes)
58    pub fn include_pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
59        let regex = regex::Regex::new(pattern)?;
60        self.include_patterns.push(regex);
61        Ok(self)
62    }
63
64    /// Check if a log message should be included
65    pub fn should_log(&self, target: &str, level: Level, message: &str) -> bool {
66        // Check include patterns first (they override excludes)
67        for pattern in &self.include_patterns {
68            if pattern.is_match(message) || pattern.is_match(target) {
69                return true;
70            }
71        }
72
73        // Check exclude patterns
74        for pattern in &self.exclude_patterns {
75            if pattern.is_match(message) || pattern.is_match(target) {
76                return false;
77            }
78        }
79
80        // Check level
81        // In tracing, levels are ordered: ERROR > WARN > INFO > DEBUG > TRACE
82        // So to check if a message should be logged, we need level <= required_level
83        let required_level = self.level_for(target).unwrap_or(self.default_level);
84        level <= required_level
85    }
86
87    /// Get the log level for a specific target
88    pub fn level_for(&self, target: &str) -> Option<Level> {
89        // Check exact match first
90        if let Some(&level) = self.component_levels.get(target) {
91            return Some(level);
92        }
93
94        // Check prefix matches (e.g., "ant_quic::connection" matches "ant_quic::connection::mod")
95        for (module, &level) in &self.component_levels {
96            if target.starts_with(module) {
97                return Some(level);
98            }
99        }
100
101        None
102    }
103}
104
105impl Default for LogFilter {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111/// Builder for creating log filters
112pub struct LogFilterBuilder {
113    filter: LogFilter,
114}
115
116impl Default for LogFilterBuilder {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl LogFilterBuilder {
123    /// Create a new filter builder
124    pub fn new() -> Self {
125        Self {
126            filter: LogFilter::new(),
127        }
128    }
129
130    /// Set default level
131    pub fn default_level(mut self, level: Level) -> Self {
132        self.filter.default_level = level;
133        self
134    }
135
136    /// Configure common QUIC components
137    pub fn quic_defaults(mut self) -> Self {
138        self.filter
139            .component_levels
140            .insert("ant_quic::connection".to_string(), Level::DEBUG);
141        self.filter
142            .component_levels
143            .insert("ant_quic::endpoint".to_string(), Level::INFO);
144        self.filter
145            .component_levels
146            .insert("ant_quic::frame".to_string(), Level::TRACE);
147        self.filter
148            .component_levels
149            .insert("ant_quic::packet".to_string(), Level::TRACE);
150        self.filter
151            .component_levels
152            .insert("ant_quic::crypto".to_string(), Level::DEBUG);
153        self.filter
154            .component_levels
155            .insert("ant_quic::transport_params".to_string(), Level::DEBUG);
156        self
157    }
158
159    /// Configure for debugging NAT traversal
160    pub fn nat_traversal_debug(mut self) -> Self {
161        self.filter
162            .component_levels
163            .insert("ant_quic::nat_traversal".to_string(), Level::TRACE);
164        self.filter
165            .component_levels
166            .insert("ant_quic::candidate_discovery".to_string(), Level::DEBUG);
167        self.filter.component_levels.insert(
168            "ant_quic::connection::nat_traversal".to_string(),
169            Level::TRACE,
170        );
171        self
172    }
173
174    /// Configure for performance analysis
175    pub fn performance_analysis(mut self) -> Self {
176        self.filter
177            .component_levels
178            .insert("ant_quic::metrics".to_string(), Level::INFO);
179        self.filter
180            .component_levels
181            .insert("ant_quic::congestion".to_string(), Level::DEBUG);
182        self.filter
183            .component_levels
184            .insert("ant_quic::pacing".to_string(), Level::DEBUG);
185        self
186    }
187
188    /// Configure for production use
189    pub fn production(mut self) -> Self {
190        self.filter.default_level = Level::WARN;
191        self.filter
192            .component_levels
193            .insert("ant_quic::connection::lifecycle".to_string(), Level::INFO);
194        self.filter
195            .component_levels
196            .insert("ant_quic::endpoint".to_string(), Level::INFO);
197        self.filter
198            .component_levels
199            .insert("ant_quic::metrics".to_string(), Level::INFO);
200        self
201    }
202
203    /// Exclude noisy components
204    pub fn quiet(mut self) -> Self {
205        // Add patterns to exclude
206        if let Ok(pattern) = regex::Regex::new(r"packet\.sent") {
207            self.filter.exclude_patterns.push(pattern);
208        }
209        if let Ok(pattern) = regex::Regex::new(r"packet\.received") {
210            self.filter.exclude_patterns.push(pattern);
211        }
212        if let Ok(pattern) = regex::Regex::new(r"frame\.sent") {
213            self.filter.exclude_patterns.push(pattern);
214        }
215        if let Ok(pattern) = regex::Regex::new(r"frame\.received") {
216            self.filter.exclude_patterns.push(pattern);
217        }
218        self
219    }
220
221    /// Build the filter
222    pub fn build(self) -> LogFilter {
223        self.filter
224    }
225}
226
227/// Dynamic filter that can be updated at runtime
228pub struct DynamicLogFilter {
229    inner: std::sync::RwLock<LogFilter>,
230}
231
232impl DynamicLogFilter {
233    /// Create a new dynamic filter
234    pub fn new(filter: LogFilter) -> Self {
235        Self {
236            inner: std::sync::RwLock::new(filter),
237        }
238    }
239
240    /// Update the filter
241    #[allow(clippy::expect_used)]
242    pub fn update<F>(&self, updater: F) -> Result<(), Box<dyn std::error::Error>>
243    where
244        F: FnOnce(&mut LogFilter) -> Result<(), Box<dyn std::error::Error>>,
245    {
246        let mut filter = self
247            .inner
248            .write()
249            .expect("Mutex poisoning is unexpected in normal operation");
250        updater(&mut filter)?;
251        Ok(())
252    }
253
254    /// Check if should log
255    #[allow(clippy::unwrap_used, clippy::expect_used)]
256    pub fn should_log(&self, target: &str, level: Level, message: &str) -> bool {
257        self.inner
258            .read()
259            .expect("Mutex poisoning is unexpected in normal operation")
260            .should_log(target, level, message)
261    }
262
263    /// Get level for target
264    #[allow(clippy::unwrap_used, clippy::expect_used)]
265    pub fn level_for(&self, target: &str) -> Option<Level> {
266        self.inner
267            .read()
268            .expect("Mutex poisoning is unexpected in normal operation")
269            .level_for(target)
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_component_filtering() {
279        let filter = LogFilterBuilder::new()
280            .default_level(Level::WARN)
281            .quic_defaults()
282            .build();
283
284        // ant_quic::connection is set to DEBUG, so it accepts ERROR, WARN, INFO, DEBUG but not TRACE
285        assert!(filter.should_log("ant_quic::connection::mod", Level::DEBUG, "test"));
286        assert!(!filter.should_log("ant_quic::connection::mod", Level::TRACE, "test"));
287
288        // ant_quic::endpoint is set to INFO, so it accepts ERROR, WARN, INFO but not DEBUG or TRACE
289        assert!(filter.should_log("ant_quic::endpoint", Level::INFO, "test"));
290        assert!(!filter.should_log("ant_quic::endpoint", Level::DEBUG, "test"));
291
292        // other::module uses default WARN, so it accepts ERROR, WARN but not INFO, DEBUG, or TRACE
293        assert!(filter.should_log("other::module", Level::WARN, "test"));
294        assert!(!filter.should_log("other::module", Level::INFO, "test"));
295    }
296
297    #[test]
298    fn test_pattern_filtering() {
299        let filter = LogFilter::new()
300            .exclude_pattern(r"noisy")
301            .unwrap()
302            .include_pattern(r"important.*noisy")
303            .unwrap();
304
305        assert!(!filter.should_log("test", Level::INFO, "this is noisy"));
306        assert!(filter.should_log("test", Level::INFO, "this is important but noisy"));
307        assert!(filter.should_log("test", Level::INFO, "this is normal"));
308    }
309}