ant_quic/logging/
filters.rs

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