use log::LevelFilter;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Filter {
global_level: LevelFilter,
module_levels: HashMap<String, LevelFilter>,
}
impl Filter {
pub fn new(level: LevelFilter) -> Self {
Self {
global_level: level,
module_levels: HashMap::new(),
}
}
pub fn with_module(mut self, module: &str, level: LevelFilter) -> Self {
self.module_levels.insert(module.to_string(), level);
self
}
pub fn global_level(&self) -> LevelFilter {
self.global_level
}
pub fn is_enabled(&self, metadata: &log::Metadata) -> bool {
let level = metadata.level();
let target = metadata.target();
if let Some(module_level) = self.find_module_level(target) {
return level <= module_level;
}
level <= self.global_level
}
fn find_module_level(&self, target: &str) -> Option<LevelFilter> {
let mut best_match: Option<(&str, LevelFilter)> = None;
for (module, &level) in &self.module_levels {
if target == module.as_str() || target.starts_with(&format!("{}::", module)) {
match best_match {
Some((current, _)) if module.len() > current.len() => {
best_match = Some((module.as_str(), level));
}
None => {
best_match = Some((module.as_str(), level));
}
_ => {}
}
}
}
best_match.map(|(_, level)| level)
}
}
impl Default for Filter {
fn default() -> Self {
Self::new(LevelFilter::Info)
}
}
#[cfg(test)]
mod tests {
use super::*;
use log::Level;
fn metadata(level: Level, target: &str) -> log::Metadata<'_> {
log::Metadata::builder().level(level).target(target).build()
}
#[test]
fn test_global_level() {
let filter = Filter::new(LevelFilter::Warn);
assert!(filter.is_enabled(&metadata(Level::Error, "any")));
assert!(filter.is_enabled(&metadata(Level::Warn, "any")));
assert!(!filter.is_enabled(&metadata(Level::Info, "any")));
assert!(!filter.is_enabled(&metadata(Level::Debug, "any")));
}
#[test]
fn test_module_override() {
let filter = Filter::new(LevelFilter::Info).with_module("noisy_lib", LevelFilter::Warn);
assert!(filter.is_enabled(&metadata(Level::Info, "my_app")));
assert!(filter.is_enabled(&metadata(Level::Warn, "noisy_lib")));
assert!(!filter.is_enabled(&metadata(Level::Info, "noisy_lib")));
assert!(!filter.is_enabled(&metadata(Level::Info, "noisy_lib::submodule")));
}
#[test]
fn test_longest_prefix_match() {
let filter = Filter::new(LevelFilter::Info)
.with_module("my_app", LevelFilter::Warn)
.with_module("my_app::db", LevelFilter::Debug);
assert!(!filter.is_enabled(&metadata(Level::Info, "my_app")));
assert!(filter.is_enabled(&metadata(Level::Debug, "my_app::db")));
assert!(!filter.is_enabled(&metadata(Level::Info, "my_app::api")));
}
}