use std::{ collections::{HashMap, VecDeque}, sync::{ OnceLock, RwLock, atomic::{AtomicU8, Ordering}, }, usize,}; use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config, private}; use log; static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None); pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;/// The maximum log level of verbosity that is enabled by default./// All messages more verbose than this level will be discarded/// by default unless specially configured.////// This is used instead of the `log::max_level` as we need to tell the `log`/// crate that the max level is everything, so that we can dynamically enable/// logs that are more verbose than this level without the `log` crate throwing/// them away before we see themstatic mut LEVEL_ENABLED_MAX_STATIC: log::LevelFilter = LEVEL_ENABLED_MAX_DEFAULT; /// A cache of the true maximum log level that _could_ be printed. This is based/// on the maximally verbose level that is configured by the user, and is used/// to filter out logs more verbose than any configured level.////// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some/// scope to print at a `debug` level, then this will be `debug`, and all/// `trace` logs will be discarded./// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`// PERF: this doesn't need to be an atomic, we don't actually care about race conditions herepub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8); const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[ #[cfg(any(target_os = "linux", target_os = "freebsd"))] ("zbus", log::LevelFilter::Warn), #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))] ("blade_graphics", log::LevelFilter::Warn), #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))] ("naga::back::spv::writer", log::LevelFilter::Warn),]; pub fn init_env_filter(filter: env_config::EnvFilter) { if let Some(level_max) = filter.level_global { unsafe { LEVEL_ENABLED_MAX_STATIC = level_max } } if ENV_FILTER.set(filter).is_err() { panic!("Environment filter cannot be initialized twice"); }} pub fn is_possibly_enabled_level(level: log::Level) -> bool { return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed);} pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Level) -> bool { // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG if !is_possibly_enabled_level(level) { // [FAST PATH] // if the message is above the maximum enabled log level // (where error < warn < info etc) then disable without checking // scope map return false; } let is_enabled_by_default = level <= unsafe { LEVEL_ENABLED_MAX_STATIC }; let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| { SCOPE_MAP.clear_poison(); return err.into_inner(); }); let Some(map) = global_scope_map.as_ref() else { // on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC return is_enabled_by_default; }; if map.is_empty() { // if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC return is_enabled_by_default; } let enabled_status = map.is_enabled(&scope, module_path, level); return match enabled_status { EnabledStatus::NotConfigured => is_enabled_by_default, EnabledStatus::Enabled => true, EnabledStatus::Disabled => false, };} pub fn refresh_from_settings(settings: &HashMap<String, String>) { let env_config = ENV_FILTER.get(); let map_new = ScopeMap::new_from_settings_and_env(settings, env_config, DEFAULT_FILTERS); let mut level_enabled_max = unsafe { LEVEL_ENABLED_MAX_STATIC }; for entry in &map_new.entries { if let Some(level) = entry.enabled { level_enabled_max = level_enabled_max.max(level); } } LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max as u8, Ordering::Release); { let mut global_map = SCOPE_MAP.write().unwrap_or_else(|err| { SCOPE_MAP.clear_poison(); err.into_inner() }); global_map.replace(map_new); } log::trace!("Log configuration updated");} fn level_filter_from_str(level_str: &str) -> Option<log::LevelFilter> { use log::LevelFilter::*; let level = match level_str.to_ascii_lowercase().as_str() { "" => Trace, "trace" => Trace, "debug" => Debug, "info" => Info, "warn" => Warn, "error" => Error, "off" => Off, "disable" | "no" | "none" | "disabled" => { crate::warn!( "Invalid log level \"{level_str}\", to disable logging set to \"off\". Defaulting to \"off\"." ); Off } _ => { crate::warn!("Invalid log level \"{level_str}\", ignoring"); return None; } }; return Some(level);} fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> { let mut scope_buf = [""; SCOPE_DEPTH_MAX]; let mut index = 0; let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR); while index < SCOPE_DEPTH_MAX { let Some(scope) = scope_iter.next() else { break; }; if scope == "" { continue; } scope_buf[index] = scope; index += 1; } if index == 0 { return None; } if let Some(_) = scope_iter.next() { crate::warn!( "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}", ); return None; } let scope = scope_buf.map(|s| s.to_string()); return Some(scope);} #[derive(Debug, PartialEq, Eq)]pub struct ScopeMap { entries: Vec<ScopeMapEntry>, modules: Vec<(String, log::LevelFilter)>, root_count: usize,} #[derive(Debug, PartialEq, Eq)]pub struct ScopeMapEntry { scope: String, enabled: Option<log::LevelFilter>, descendants: std::ops::Range<usize>,} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum EnabledStatus { Enabled, Disabled, NotConfigured,} impl ScopeMap { pub fn new_from_settings_and_env( items_input_map: &HashMap<String, String>, env_config: Option<&env_config::EnvFilter>, default_filters: &[(&str, log::LevelFilter)], ) -> Self { let mut items = Vec::<(ScopeAlloc, log::LevelFilter)>::with_capacity( items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()) + default_filters.len(), ); let mut modules = Vec::with_capacity(4); let env_filters = env_config.iter().flat_map(|env_filter| { env_filter .directive_names .iter() .zip(env_filter.directive_levels.iter()) .map(|(scope_str, level_filter)| (scope_str.as_str(), *level_filter)) }); let new_filters = items_input_map .into_iter() .filter_map(|(scope_str, level_str)| { let level_filter = level_filter_from_str(level_str)?; Some((scope_str.as_str(), level_filter)) }); let all_filters = default_filters .iter() .cloned() .chain(env_filters) .chain(new_filters); for (scope_str, level_filter) in all_filters { if scope_str.contains("::") { if let Some(idx) = modules.iter().position(|(module, _)| module == scope_str) { modules[idx].1 = level_filter; } else { modules.push((scope_str.to_string(), level_filter)); } continue; } let Some(scope) = scope_alloc_from_scope_str(scope_str) else { continue; }; if let Some(idx) = items .iter() .position(|(scope_existing, _)| scope_existing == &scope) { items[idx].1 = level_filter; } else { items.push((scope, level_filter)); } } items.sort_by(|a, b| a.0.cmp(&b.0)); modules.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name)); let mut this = Self { entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX), modules, root_count: 0, }; let items_count = items.len(); struct ProcessQueueEntry { parent_index: usize, depth: usize, items_range: std::ops::Range<usize>, } let mut process_queue = VecDeque::new(); process_queue.push_back(ProcessQueueEntry { parent_index: usize::MAX, depth: 0, items_range: 0..items_count, }); let empty_range = 0..0; while let Some(process_entry) = process_queue.pop_front() { let ProcessQueueEntry { items_range, depth, parent_index, } = process_entry; let mut cursor = items_range.start; let res_entries_start = this.entries.len(); while cursor < items_range.end { let sub_items_start = cursor; cursor += 1; let scope_name = &items[sub_items_start].0[depth]; while cursor < items_range.end && &items[cursor].0[depth] == scope_name { cursor += 1; } let sub_items_end = cursor; if scope_name == "" { assert_eq!(sub_items_start + 1, sub_items_end); assert_ne!(depth, 0); assert_ne!(parent_index, usize::MAX); assert!(this.entries[parent_index].enabled.is_none()); this.entries[parent_index].enabled = Some(items[sub_items_start].1); continue; } let is_valid_scope = scope_name != ""; let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope; let mut enabled = None; if is_last { assert_eq!( sub_items_start + 1, sub_items_end, "Expected one item: got: {:?}", &items[items_range.clone()] ); enabled = Some(items[sub_items_start].1); } else { let entry_index = this.entries.len(); process_queue.push_back(ProcessQueueEntry { items_range: sub_items_start..sub_items_end, parent_index: entry_index, depth: depth + 1, }); } this.entries.push(ScopeMapEntry { scope: scope_name.to_owned(), enabled, descendants: empty_range.clone(), }); } let res_entries_end = this.entries.len(); if parent_index != usize::MAX { this.entries[parent_index].descendants = res_entries_start..res_entries_end; } else { this.root_count = res_entries_end; } } return this; } pub fn is_empty(&self) -> bool { self.entries.is_empty() && self.modules.is_empty() } pub fn is_enabled<S>( &self, scope: &[S; SCOPE_DEPTH_MAX], module_path: Option<&str>, level: log::Level, ) -> EnabledStatus where S: AsRef<str>, { fn search<S>(map: &ScopeMap, scope: &[S; SCOPE_DEPTH_MAX]) -> Option<log::LevelFilter> where S: AsRef<str>, { let mut enabled = None; let mut cur_range = &map.entries[0..map.root_count]; let mut depth = 0; 'search: while !cur_range.is_empty() && depth < SCOPE_DEPTH_MAX && scope[depth].as_ref() != "" { for entry in cur_range { if entry.scope == scope[depth].as_ref() { enabled = entry.enabled.or(enabled); cur_range = &map.entries[entry.descendants.clone()]; depth += 1; continue 'search; } } break 'search; } return enabled; } let mut enabled = search(self, scope); if let Some(module_path) = module_path { let scope_is_empty = scope[0].as_ref().is_empty(); if enabled.is_none() && scope_is_empty { let crate_name = private::extract_crate_name_from_module_path(module_path); let mut crate_name_scope = [""; SCOPE_DEPTH_MAX]; crate_name_scope[0] = crate_name; enabled = search(self, &crate_name_scope); } if !self.modules.is_empty() { let crate_name = private::extract_crate_name_from_module_path(module_path); let is_scope_just_crate_name = scope[0].as_ref() == crate_name && scope[1].as_ref() == ""; if enabled.is_none() || is_scope_just_crate_name { for (module, filter) in &self.modules { if module == module_path { enabled.replace(*filter); break; } } } } } if let Some(enabled_filter) = enabled { if level <= enabled_filter { return EnabledStatus::Enabled; } return EnabledStatus::Disabled; } return EnabledStatus::NotConfigured; }} #[cfg(test)]mod tests { use log::LevelFilter; use crate::private::scope_new; use super::*; fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap { let hash_map: HashMap<String, String> = kv .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); ScopeMap::new_from_settings_and_env(&hash_map, None, &[]) } #[test] fn test_initialization() { let map = scope_map_from_keys(&[("a.b.c.d", "trace")]); assert_eq!(map.root_count, 1); assert_eq!(map.entries.len(), 4); let map = scope_map_from_keys(&[]); assert_eq!(map.root_count, 0); assert_eq!(map.entries.len(), 0); let map = scope_map_from_keys(&[("", "trace")]); assert_eq!(map.root_count, 0); assert_eq!(map.entries.len(), 0); let map = scope_map_from_keys(&[("foo..bar", "trace")]); assert_eq!(map.root_count, 1); assert_eq!(map.entries.len(), 2); let map = scope_map_from_keys(&[ ("a.b.c.d", "trace"), ("e.f.g.h", "debug"), ("i.j.k.l", "info"), ("m.n.o.p", "warn"), ("q.r.s.t", "error"), ]); assert_eq!(map.root_count, 5); assert_eq!(map.entries.len(), 20); assert_eq!(map.entries[0].scope, "a"); assert_eq!(map.entries[1].scope, "e"); assert_eq!(map.entries[2].scope, "i"); assert_eq!(map.entries[3].scope, "m"); assert_eq!(map.entries[4].scope, "q"); } fn scope_from_scope_str(scope_str: &'static str) -> Scope { let mut scope_buf = [""; SCOPE_DEPTH_MAX]; let mut index = 0; let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR); while index < SCOPE_DEPTH_MAX { let Some(scope) = scope_iter.next() else { break; }; if scope == "" { continue; } scope_buf[index] = scope; index += 1; } assert_ne!(index, 0); assert!(scope_iter.next().is_none()); return scope_buf; } #[test] fn test_is_enabled() { let map = scope_map_from_keys(&[ ("a.b.c.d", "trace"), ("e.f.g.h", "debug"), ("i.j.k.l", "info"), ("m.n.o.p", "warn"), ("q.r.s.t", "error"), ]); use log::Level; assert_eq!( map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace), EnabledStatus::Disabled );Lines 1–500 of 836. Load more
use std::{ collections::{HashMap, VecDeque}, sync::{ OnceLock, RwLock, atomic::{AtomicU8, Ordering}, }, usize, }; use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config, private}; use log; static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new(); static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None); pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info; /// The maximum log level of verbosity that is enabled by default. /// All messages more verbose than this level will be discarded /// by default unless specially configured. /// /// This is used instead of the `log::max_level` as we need to tell the `log` /// crate that the max level is everything, so that we can dynamically enable /// logs that are more verbose than this level without the `log` crate throwing /// them away before we see them static mut LEVEL_ENABLED_MAX_STATIC: log::LevelFilter = LEVEL_ENABLED_MAX_DEFAULT; /// A cache of the true maximum log level that _could_ be printed. This is based /// on the maximally verbose level that is configured by the user, and is used /// to filter out logs more verbose than any configured level. /// /// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some /// scope to print at a `debug` level, then this will be `debug`, and all /// `trace` logs will be discarded. /// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC` // PERF: this doesn't need to be an atomic, we don't actually care about race conditions here pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8); const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[ #[cfg(any(target_os = "linux", target_os = "freebsd"))] ("zbus", log::LevelFilter::Warn), #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))] ("blade_graphics", log::LevelFilter::Warn), #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))] ("naga::back::spv::writer", log::LevelFilter::Warn), ]; pub fn init_env_filter(filter: env_config::EnvFilter) { if let Some(level_max) = filter.level_global { unsafe { LEVEL_ENABLED_MAX_STATIC = level_max } } if ENV_FILTER.set(filter).is_err() { panic!("Environment filter cannot be initialized twice"); } } pub fn is_possibly_enabled_level(level: log::Level) -> bool { return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed); } pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Level) -> bool { // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG if !is_possibly_enabled_level(level) { // [FAST PATH] // if the message is above the maximum enabled log level // (where error < warn < info etc) then disable without checking // scope map return false; } let is_enabled_by_default = level <= unsafe { LEVEL_ENABLED_MAX_STATIC }; let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| { SCOPE_MAP.clear_poison(); return err.into_inner(); }); let Some(map) = global_scope_map.as_ref() else { // on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC return is_enabled_by_default; }; if map.is_empty() { // if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC return is_enabled_by_default; } let enabled_status = map.is_enabled(&scope, module_path, level); return match enabled_status { EnabledStatus::NotConfigured => is_enabled_by_default, EnabledStatus::Enabled => true, EnabledStatus::Disabled => false, }; } pub fn refresh_from_settings(settings: &HashMap<String, String>) { let env_config = ENV_FILTER.get(); let map_new = ScopeMap::new_from_settings_and_env(settings, env_config, DEFAULT_FILTERS); let mut level_enabled_max = unsafe { LEVEL_ENABLED_MAX_STATIC }; for entry in &map_new.entries { if let Some(level) = entry.enabled { level_enabled_max = level_enabled_max.max(level); } } LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max as u8, Ordering::Release); { let mut global_map = SCOPE_MAP.write().unwrap_or_else(|err| { SCOPE_MAP.clear_poison(); err.into_inner() }); global_map.replace(map_new); } log::trace!("Log configuration updated"); } fn level_filter_from_str(level_str: &str) -> Option<log::LevelFilter> { use log::LevelFilter::*; let level = match level_str.to_ascii_lowercase().as_str() { "" => Trace, "trace" => Trace, "debug" => Debug, "info" => Info, "warn" => Warn, "error" => Error, "off" => Off, "disable" | "no" | "none" | "disabled" => { crate::warn!( "Invalid log level \"{level_str}\", to disable logging set to \"off\". Defaulting to \"off\"." ); Off } _ => { crate::warn!("Invalid log level \"{level_str}\", ignoring"); return None; } }; return Some(level); } fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> { let mut scope_buf = [""; SCOPE_DEPTH_MAX]; let mut index = 0; let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR); while index < SCOPE_DEPTH_MAX { let Some(scope) = scope_iter.next() else { break; }; if scope == "" { continue; } scope_buf[index] = scope; index += 1; } if index == 0 { return None; } if let Some(_) = scope_iter.next() { crate::warn!( "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}", ); return None; } let scope = scope_buf.map(|s| s.to_string()); return Some(scope); } #[derive(Debug, PartialEq, Eq)] pub struct ScopeMap { entries: Vec<ScopeMapEntry>, modules: Vec<(String, log::LevelFilter)>, root_count: usize, } #[derive(Debug, PartialEq, Eq)] pub struct ScopeMapEntry { scope: String, enabled: Option<log::LevelFilter>, descendants: std::ops::Range<usize>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EnabledStatus { Enabled, Disabled, NotConfigured, } impl ScopeMap { pub fn new_from_settings_and_env( items_input_map: &HashMap<String, String>, env_config: Option<&env_config::EnvFilter>, default_filters: &[(&str, log::LevelFilter)], ) -> Self { let mut items = Vec::<(ScopeAlloc, log::LevelFilter)>::with_capacity( items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()) + default_filters.len(), ); let mut modules = Vec::with_capacity(4); let env_filters = env_config.iter().flat_map(|env_filter| { env_filter .directive_names .iter() .zip(env_filter.directive_levels.iter()) .map(|(scope_str, level_filter)| (scope_str.as_str(), *level_filter)) }); let new_filters = items_input_map .into_iter() .filter_map(|(scope_str, level_str)| { let level_filter = level_filter_from_str(level_str)?; Some((scope_str.as_str(), level_filter)) }); let all_filters = default_filters .iter() .cloned() .chain(env_filters) .chain(new_filters); for (scope_str, level_filter) in all_filters { if scope_str.contains("::") { if let Some(idx) = modules.iter().position(|(module, _)| module == scope_str) { modules[idx].1 = level_filter; } else { modules.push((scope_str.to_string(), level_filter)); } continue; } let Some(scope) = scope_alloc_from_scope_str(scope_str) else { continue; }; if let Some(idx) = items .iter() .position(|(scope_existing, _)| scope_existing == &scope) { items[idx].1 = level_filter; } else { items.push((scope, level_filter)); } } items.sort_by(|a, b| a.0.cmp(&b.0)); modules.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name)); let mut this = Self { entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX), modules, root_count: 0, }; let items_count = items.len(); struct ProcessQueueEntry { parent_index: usize, depth: usize, items_range: std::ops::Range<usize>, } let mut process_queue = VecDeque::new(); process_queue.push_back(ProcessQueueEntry { parent_index: usize::MAX, depth: 0, items_range: 0..items_count, }); let empty_range = 0..0; while let Some(process_entry) = process_queue.pop_front() { let ProcessQueueEntry { items_range, depth, parent_index, } = process_entry; let mut cursor = items_range.start; let res_entries_start = this.entries.len(); while cursor < items_range.end { let sub_items_start = cursor; cursor += 1; let scope_name = &items[sub_items_start].0[depth]; while cursor < items_range.end && &items[cursor].0[depth] == scope_name { cursor += 1; } let sub_items_end = cursor; if scope_name == "" { assert_eq!(sub_items_start + 1, sub_items_end); assert_ne!(depth, 0); assert_ne!(parent_index, usize::MAX); assert!(this.entries[parent_index].enabled.is_none()); this.entries[parent_index].enabled = Some(items[sub_items_start].1); continue; } let is_valid_scope = scope_name != ""; let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope; let mut enabled = None; if is_last { assert_eq!( sub_items_start + 1, sub_items_end, "Expected one item: got: {:?}", &items[items_range.clone()] ); enabled = Some(items[sub_items_start].1); } else { let entry_index = this.entries.len(); process_queue.push_back(ProcessQueueEntry { items_range: sub_items_start..sub_items_end, parent_index: entry_index, depth: depth + 1, }); } this.entries.push(ScopeMapEntry { scope: scope_name.to_owned(), enabled, descendants: empty_range.clone(), }); } let res_entries_end = this.entries.len(); if parent_index != usize::MAX { this.entries[parent_index].descendants = res_entries_start..res_entries_end; } else { this.root_count = res_entries_end; } } return this; } pub fn is_empty(&self) -> bool { self.entries.is_empty() && self.modules.is_empty() } pub fn is_enabled<S>( &self, scope: &[S; SCOPE_DEPTH_MAX], module_path: Option<&str>, level: log::Level, ) -> EnabledStatus where S: AsRef<str>, { fn search<S>(map: &ScopeMap, scope: &[S; SCOPE_DEPTH_MAX]) -> Option<log::LevelFilter> where S: AsRef<str>, { let mut enabled = None; let mut cur_range = &map.entries[0..map.root_count]; let mut depth = 0; 'search: while !cur_range.is_empty() && depth < SCOPE_DEPTH_MAX && scope[depth].as_ref() != "" { for entry in cur_range { if entry.scope == scope[depth].as_ref() { enabled = entry.enabled.or(enabled); cur_range = &map.entries[entry.descendants.clone()]; depth += 1; continue 'search; } } break 'search; } return enabled; } let mut enabled = search(self, scope); if let Some(module_path) = module_path { let scope_is_empty = scope[0].as_ref().is_empty(); if enabled.is_none() && scope_is_empty { let crate_name = private::extract_crate_name_from_module_path(module_path); let mut crate_name_scope = [""; SCOPE_DEPTH_MAX]; crate_name_scope[0] = crate_name; enabled = search(self, &crate_name_scope); } if !self.modules.is_empty() { let crate_name = private::extract_crate_name_from_module_path(module_path); let is_scope_just_crate_name = scope[0].as_ref() == crate_name && scope[1].as_ref() == ""; if enabled.is_none() || is_scope_just_crate_name { for (module, filter) in &self.modules { if module == module_path { enabled.replace(*filter); break; } } } } } if let Some(enabled_filter) = enabled { if level <= enabled_filter { return EnabledStatus::Enabled; } return EnabledStatus::Disabled; } return EnabledStatus::NotConfigured; } } #[cfg(test)] mod tests { use log::LevelFilter; use crate::private::scope_new; use super::*; fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap { let hash_map: HashMap<String, String> = kv .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); ScopeMap::new_from_settings_and_env(&hash_map, None, &[]) } #[test] fn test_initialization() { let map = scope_map_from_keys(&[("a.b.c.d", "trace")]); assert_eq!(map.root_count, 1); assert_eq!(map.entries.len(), 4); let map = scope_map_from_keys(&[]); assert_eq!(map.root_count, 0); assert_eq!(map.entries.len(), 0); let map = scope_map_from_keys(&[("", "trace")]); assert_eq!(map.root_count, 0); assert_eq!(map.entries.len(), 0); let map = scope_map_from_keys(&[("foo..bar", "trace")]); assert_eq!(map.root_count, 1); assert_eq!(map.entries.len(), 2); let map = scope_map_from_keys(&[ ("a.b.c.d", "trace"), ("e.f.g.h", "debug"), ("i.j.k.l", "info"), ("m.n.o.p", "warn"), ("q.r.s.t", "error"), ]); assert_eq!(map.root_count, 5); assert_eq!(map.entries.len(), 20); assert_eq!(map.entries[0].scope, "a"); assert_eq!(map.entries[1].scope, "e"); assert_eq!(map.entries[2].scope, "i"); assert_eq!(map.entries[3].scope, "m"); assert_eq!(map.entries[4].scope, "q"); } fn scope_from_scope_str(scope_str: &'static str) -> Scope { let mut scope_buf = [""; SCOPE_DEPTH_MAX]; let mut index = 0; let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR); while index < SCOPE_DEPTH_MAX { let Some(scope) = scope_iter.next() else { break; }; if scope == "" { continue; } scope_buf[index] = scope; index += 1; } assert_ne!(index, 0); assert!(scope_iter.next().is_none()); return scope_buf; } #[test] fn test_is_enabled() { let map = scope_map_from_keys(&[ ("a.b.c.d", "trace"), ("e.f.g.h", "debug"), ("i.j.k.l", "info"), ("m.n.o.p", "warn"), ("q.r.s.t", "error"), ]); use log::Level; assert_eq!( map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info), EnabledStatus::Enabled ); assert_eq!( map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace), EnabledStatus::Disabled );