use std::collections::HashMap;
use tracing::Level;
#[derive(Debug, Clone)]
pub struct LogFilter {
component_levels: HashMap<String, Level>,
default_level: Level,
exclude_patterns: Vec<regex::Regex>,
include_patterns: Vec<regex::Regex>,
}
impl LogFilter {
pub fn new() -> Self {
Self {
component_levels: HashMap::new(),
default_level: Level::INFO,
exclude_patterns: Vec::new(),
include_patterns: Vec::new(),
}
}
pub fn with_default_level(mut self, level: Level) -> Self {
self.default_level = level;
self
}
pub fn with_module(mut self, module: &str, level: Level) -> Self {
self.component_levels.insert(module.to_string(), level);
self
}
pub fn exclude_pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
let regex = regex::Regex::new(pattern)?;
self.exclude_patterns.push(regex);
Ok(self)
}
pub fn include_pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
let regex = regex::Regex::new(pattern)?;
self.include_patterns.push(regex);
Ok(self)
}
pub fn should_log(&self, target: &str, level: Level, message: &str) -> bool {
for pattern in &self.include_patterns {
if pattern.is_match(message) || pattern.is_match(target) {
return true;
}
}
for pattern in &self.exclude_patterns {
if pattern.is_match(message) || pattern.is_match(target) {
return false;
}
}
let required_level = self.level_for(target).unwrap_or(self.default_level);
level <= required_level
}
pub fn level_for(&self, target: &str) -> Option<Level> {
if let Some(&level) = self.component_levels.get(target) {
return Some(level);
}
for (module, &level) in &self.component_levels {
if target.starts_with(module) {
return Some(level);
}
}
None
}
}
impl Default for LogFilter {
fn default() -> Self {
Self::new()
}
}
pub struct LogFilterBuilder {
filter: LogFilter,
}
impl Default for LogFilterBuilder {
fn default() -> Self {
Self::new()
}
}
impl LogFilterBuilder {
pub fn new() -> Self {
Self {
filter: LogFilter::new(),
}
}
pub fn default_level(mut self, level: Level) -> Self {
self.filter.default_level = level;
self
}
pub fn quic_defaults(mut self) -> Self {
self.filter
.component_levels
.insert("ant_quic::connection".to_string(), Level::DEBUG);
self.filter
.component_levels
.insert("ant_quic::endpoint".to_string(), Level::INFO);
self.filter
.component_levels
.insert("ant_quic::frame".to_string(), Level::TRACE);
self.filter
.component_levels
.insert("ant_quic::packet".to_string(), Level::TRACE);
self.filter
.component_levels
.insert("ant_quic::crypto".to_string(), Level::DEBUG);
self.filter
.component_levels
.insert("ant_quic::transport_params".to_string(), Level::DEBUG);
self
}
pub fn nat_traversal_debug(mut self) -> Self {
self.filter
.component_levels
.insert("ant_quic::nat_traversal".to_string(), Level::TRACE);
self.filter
.component_levels
.insert("ant_quic::candidate_discovery".to_string(), Level::DEBUG);
self.filter.component_levels.insert(
"ant_quic::connection::nat_traversal".to_string(),
Level::TRACE,
);
self
}
pub fn performance_analysis(mut self) -> Self {
self.filter
.component_levels
.insert("ant_quic::metrics".to_string(), Level::INFO);
self.filter
.component_levels
.insert("ant_quic::congestion".to_string(), Level::DEBUG);
self.filter
.component_levels
.insert("ant_quic::pacing".to_string(), Level::DEBUG);
self
}
pub fn production(mut self) -> Self {
self.filter.default_level = Level::WARN;
self.filter
.component_levels
.insert("ant_quic::connection::lifecycle".to_string(), Level::INFO);
self.filter
.component_levels
.insert("ant_quic::endpoint".to_string(), Level::INFO);
self.filter
.component_levels
.insert("ant_quic::metrics".to_string(), Level::INFO);
self
}
pub fn quiet(mut self) -> Self {
if let Ok(pattern) = regex::Regex::new(r"packet\.sent") {
self.filter.exclude_patterns.push(pattern);
}
if let Ok(pattern) = regex::Regex::new(r"packet\.received") {
self.filter.exclude_patterns.push(pattern);
}
if let Ok(pattern) = regex::Regex::new(r"frame\.sent") {
self.filter.exclude_patterns.push(pattern);
}
if let Ok(pattern) = regex::Regex::new(r"frame\.received") {
self.filter.exclude_patterns.push(pattern);
}
self
}
pub fn build(self) -> LogFilter {
self.filter
}
}
pub struct DynamicLogFilter {
inner: parking_lot::RwLock<LogFilter>,
}
impl DynamicLogFilter {
pub fn new(filter: LogFilter) -> Self {
Self {
inner: parking_lot::RwLock::new(filter),
}
}
pub fn update<F>(&self, updater: F) -> Result<(), Box<dyn std::error::Error>>
where
F: FnOnce(&mut LogFilter) -> Result<(), Box<dyn std::error::Error>>,
{
let mut filter = self.inner.write();
updater(&mut filter)?;
Ok(())
}
pub fn should_log(&self, target: &str, level: Level, message: &str) -> bool {
self.inner.read().should_log(target, level, message)
}
pub fn level_for(&self, target: &str) -> Option<Level> {
self.inner.read().level_for(target)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_component_filtering() {
let filter = LogFilterBuilder::new()
.default_level(Level::WARN)
.quic_defaults()
.build();
assert!(filter.should_log("ant_quic::connection::mod", Level::DEBUG, "test"));
assert!(!filter.should_log("ant_quic::connection::mod", Level::TRACE, "test"));
assert!(filter.should_log("ant_quic::endpoint", Level::INFO, "test"));
assert!(!filter.should_log("ant_quic::endpoint", Level::DEBUG, "test"));
assert!(filter.should_log("other::module", Level::WARN, "test"));
assert!(!filter.should_log("other::module", Level::INFO, "test"));
}
#[test]
fn test_pattern_filtering() {
let filter = LogFilter::new()
.exclude_pattern(r"noisy")
.unwrap()
.include_pattern(r"important.*noisy")
.unwrap();
assert!(!filter.should_log("test", Level::INFO, "this is noisy"));
assert!(filter.should_log("test", Level::INFO, "this is important but noisy"));
assert!(filter.should_log("test", Level::INFO, "this is normal"));
}
}