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"));
}
#[test]
fn default_filter_uses_info_level() {
let filter = LogFilter::default();
assert!(filter.should_log("ant_quic::unknown", Level::ERROR, "error"));
assert!(filter.should_log("ant_quic::unknown", Level::WARN, "warn"));
assert!(filter.should_log("ant_quic::unknown", Level::INFO, "info"));
assert!(!filter.should_log("ant_quic::unknown", Level::DEBUG, "debug"));
assert!(!filter.should_log("ant_quic::unknown", Level::TRACE, "trace"));
assert_eq!(filter.level_for("ant_quic::unknown"), None);
}
#[test]
fn exact_module_match_takes_configured_level() {
let filter = LogFilter::new().with_module("ant_quic::connection", Level::WARN);
assert_eq!(filter.level_for("ant_quic::connection"), Some(Level::WARN));
assert_eq!(
filter.level_for("ant_quic::connection::streams"),
Some(Level::WARN)
);
assert!(filter.should_log("ant_quic::connection", Level::WARN, "warn"));
assert!(!filter.should_log("ant_quic::connection::streams", Level::INFO, "info"));
}
#[test]
fn include_pattern_matches_target_and_overrides_exclude() {
let filter = LogFilter::new()
.exclude_pattern(r"ant_quic::packet")
.unwrap()
.include_pattern(r"ant_quic::packet::important")
.unwrap();
assert!(!filter.should_log("ant_quic::packet", Level::INFO, "normal packet"));
assert!(filter.should_log("ant_quic::packet::important", Level::TRACE, "trace packet"));
}
#[test]
fn invalid_regex_is_reported() {
assert!(LogFilter::new().exclude_pattern("[").is_err());
assert!(LogFilter::new().include_pattern("[").is_err());
}
#[test]
fn builder_presets_set_expected_levels() {
let nat = LogFilterBuilder::new().nat_traversal_debug().build();
assert_eq!(nat.level_for("ant_quic::nat_traversal"), Some(Level::TRACE));
assert_eq!(
nat.level_for("ant_quic::candidate_discovery"),
Some(Level::DEBUG)
);
let perf = LogFilterBuilder::new().performance_analysis().build();
assert_eq!(perf.level_for("ant_quic::metrics"), Some(Level::INFO));
assert_eq!(perf.level_for("ant_quic::congestion"), Some(Level::DEBUG));
let prod = LogFilterBuilder::new().production().build();
assert_eq!(
prod.level_for("ant_quic::connection::lifecycle"),
Some(Level::INFO)
);
assert!(!prod.should_log("ant_quic::misc", Level::INFO, "info"));
assert!(prod.should_log("ant_quic::misc", Level::WARN, "warn"));
}
#[test]
fn quiet_excludes_noisy_packet_and_frame_messages() {
let filter = LogFilterBuilder::new().quiet().build();
for message in [
"packet.sent",
"packet.received",
"frame.sent",
"frame.received",
] {
assert!(!filter.should_log("ant_quic::test", Level::INFO, message));
}
assert!(filter.should_log("ant_quic::test", Level::INFO, "connection.established"));
}
#[test]
fn dynamic_filter_update_changes_decision() {
let dynamic = DynamicLogFilter::new(LogFilter::new());
assert!(!dynamic.should_log("ant_quic::connection", Level::DEBUG, "before"));
dynamic
.update(|filter| {
*filter = filter
.clone()
.with_module("ant_quic::connection", Level::DEBUG);
Ok(())
})
.unwrap();
assert!(dynamic.should_log("ant_quic::connection", Level::DEBUG, "after"));
assert_eq!(
dynamic.level_for("ant_quic::connection"),
Some(Level::DEBUG)
);
}
#[test]
fn dynamic_filter_update_propagates_errors_without_mutation() {
let dynamic = DynamicLogFilter::new(LogFilter::new().with_default_level(Level::WARN));
let result = dynamic.update(|_| Err("scripted update failure".into()));
assert!(result.is_err());
assert!(!dynamic.should_log("ant_quic::misc", Level::INFO, "still warn"));
assert!(dynamic.should_log("ant_quic::misc", Level::WARN, "still warn"));
}
}