zlog: Module-level configuration and other improvements (#29161)
Various improvements to `zlog` including: - Enable filtering by module (reproducing `env_logger` behavior) both through env and settings. - Note: filtering by module currently does not account for parent module configuration, but does account for crate configuration. i.e. `crate=trace` will enable `TRACE` messages in `crate::a` and `crate:🅰️:b` modules, but `crate::a=trace` will not enable trace messages in module `crate:🅰️:b` - Implementing the `Log` trait for `zlog::Logger` to support gradual transition and evaluate tradeoffs of always going through `log` crate. - Added the ability to turn off logging for a specific filter (module or scope) completely by setting it to `off` (in env: `crate::a=off`, in settings: `"project.foo": "off"`) - Made it so the `zlog::scoped!` macro can be used in constant expressions, so scoped loggers can be declared as global constants Release Notes: - N/A
This commit is contained in:
parent
f8ac6eef75
commit
d13cd007a2
4 changed files with 211 additions and 118 deletions
|
@ -18,12 +18,12 @@ pub fn parse(filter: &str) -> Result<EnvFilter> {
|
||||||
return Err(anyhow!("Invalid directive: {}", directive));
|
return Err(anyhow!("Invalid directive: {}", directive));
|
||||||
}
|
}
|
||||||
let level = parse_level(level.trim())?;
|
let level = parse_level(level.trim())?;
|
||||||
directive_names.push(name.to_string());
|
directive_names.push(name.trim().trim_end_matches(".rs").to_string());
|
||||||
directive_levels.push(level);
|
directive_levels.push(level);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let Ok(level) = parse_level(directive.trim()) else {
|
let Ok(level) = parse_level(directive.trim()) else {
|
||||||
directive_names.push(directive.trim().to_string());
|
directive_names.push(directive.trim().trim_end_matches(".rs").to_string());
|
||||||
directive_levels.push(log::LevelFilter::max() /* Enable all levels */);
|
directive_levels.push(log::LevelFilter::max() /* Enable all levels */);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
hash::{DefaultHasher, Hasher},
|
|
||||||
sync::{
|
sync::{
|
||||||
OnceLock, RwLock,
|
OnceLock, RwLock,
|
||||||
atomic::{AtomicU8, Ordering},
|
atomic::{AtomicU8, Ordering},
|
||||||
|
@ -13,11 +12,7 @@ use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config
|
||||||
use log;
|
use log;
|
||||||
|
|
||||||
static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
|
static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
|
||||||
static SCOPE_MAP: RwLock<Option<GlobalScopeMap>> = RwLock::new(None);
|
static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None);
|
||||||
struct GlobalScopeMap {
|
|
||||||
map: ScopeMap,
|
|
||||||
hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
|
const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
|
||||||
/// The maximum log level of verbosity that is enabled by default.
|
/// The maximum log level of verbosity that is enabled by default.
|
||||||
|
@ -54,7 +49,7 @@ pub fn is_possibly_enabled_level(level: log::Level) -> bool {
|
||||||
return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed);
|
return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_scope_enabled(scope: &Scope, level: log::Level) -> bool {
|
pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Level) -> bool {
|
||||||
if level <= unsafe { LEVEL_ENABLED_MAX_STATIC } {
|
if level <= unsafe { LEVEL_ENABLED_MAX_STATIC } {
|
||||||
// [FAST PATH]
|
// [FAST PATH]
|
||||||
// if the message is at or below the minimum printed log level
|
// if the message is at or below the minimum printed log level
|
||||||
|
@ -73,7 +68,7 @@ pub fn is_scope_enabled(scope: &Scope, level: log::Level) -> bool {
|
||||||
return err.into_inner();
|
return err.into_inner();
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(GlobalScopeMap { map, .. }) = global_scope_map.as_ref() else {
|
let Some(map) = global_scope_map.as_ref() else {
|
||||||
// on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
|
// on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -82,7 +77,7 @@ pub fn is_scope_enabled(scope: &Scope, level: log::Level) -> bool {
|
||||||
// if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
|
// if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let enabled_status = map.is_enabled(&scope, level);
|
let enabled_status = map.is_enabled(&scope, module_path, level);
|
||||||
return match enabled_status {
|
return match enabled_status {
|
||||||
// if it isn't configured, then it it's disabled because it's not <= LEVEL_ENABLED_MAX_STATIC
|
// if it isn't configured, then it it's disabled because it's not <= LEVEL_ENABLED_MAX_STATIC
|
||||||
EnabledStatus::NotConfigured => false,
|
EnabledStatus::NotConfigured => false,
|
||||||
|
@ -91,69 +86,46 @@ pub fn is_scope_enabled(scope: &Scope, level: log::Level) -> bool {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_scope_map_settings(map: &HashMap<String, String>) -> u64 {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
let mut items = map.iter().collect::<Vec<_>>();
|
|
||||||
items.sort();
|
|
||||||
for (key, value) in items {
|
|
||||||
Hasher::write(&mut hasher, key.as_bytes());
|
|
||||||
Hasher::write(&mut hasher, value.as_bytes());
|
|
||||||
}
|
|
||||||
return hasher.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn refresh() {
|
pub(crate) fn refresh() {
|
||||||
refresh_from_settings(&HashMap::default());
|
refresh_from_settings(&HashMap::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_from_settings(settings: &HashMap<String, String>) {
|
pub fn refresh_from_settings(settings: &HashMap<String, String>) {
|
||||||
let hash_old = {
|
|
||||||
SCOPE_MAP
|
|
||||||
.read()
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
SCOPE_MAP.clear_poison();
|
|
||||||
err.into_inner()
|
|
||||||
})
|
|
||||||
.as_ref()
|
|
||||||
.map(|scope_map| scope_map.hash)
|
|
||||||
};
|
|
||||||
let hash_new = hash_scope_map_settings(settings);
|
|
||||||
if hash_old == Some(hash_new) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let env_config = ENV_FILTER.get();
|
let env_config = ENV_FILTER.get();
|
||||||
let map_new = ScopeMap::new_from_settings_and_env(settings, env_config);
|
let map_new = ScopeMap::new_from_settings_and_env(settings, env_config);
|
||||||
let mut level_enabled_max = unsafe { LEVEL_ENABLED_MAX_STATIC };
|
let mut level_enabled_max = unsafe { LEVEL_ENABLED_MAX_STATIC };
|
||||||
for entry in &map_new.entries {
|
for entry in &map_new.entries {
|
||||||
if let Some(level) = entry.enabled {
|
if let Some(level) = entry.enabled {
|
||||||
level_enabled_max = level_enabled_max.max(level.to_level_filter());
|
level_enabled_max = level_enabled_max.max(level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max as u8, Ordering::Release);
|
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();
|
let mut global_map = SCOPE_MAP.write().unwrap_or_else(|err| {
|
||||||
err.into_inner()
|
SCOPE_MAP.clear_poison();
|
||||||
});
|
err.into_inner()
|
||||||
global_map.replace(GlobalScopeMap {
|
});
|
||||||
map: map_new,
|
global_map.replace(map_new);
|
||||||
hash: hash_new,
|
}
|
||||||
});
|
log::trace!("Log configuration updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level_from_level_str(level_str: &String) -> Option<log::Level> {
|
fn level_filter_from_str(level_str: &String) -> Option<log::LevelFilter> {
|
||||||
|
use log::LevelFilter::*;
|
||||||
let level = match level_str.to_ascii_lowercase().as_str() {
|
let level = match level_str.to_ascii_lowercase().as_str() {
|
||||||
"" => log::Level::Trace,
|
"" => Trace,
|
||||||
"trace" => log::Level::Trace,
|
"trace" => Trace,
|
||||||
"debug" => log::Level::Debug,
|
"debug" => Debug,
|
||||||
"info" => log::Level::Info,
|
"info" => Info,
|
||||||
"warn" => log::Level::Warn,
|
"warn" => Warn,
|
||||||
"error" => log::Level::Error,
|
"error" => Error,
|
||||||
"off" | "disable" | "no" | "none" | "disabled" => {
|
"off" => Off,
|
||||||
|
"disable" | "no" | "none" | "disabled" => {
|
||||||
crate::warn!(
|
crate::warn!(
|
||||||
"Invalid log level \"{level_str}\", set to error to disable non-error logging. Defaulting to error"
|
"Invalid log level \"{level_str}\", to disable logging set to \"off\". Defaulting to \"off\"."
|
||||||
);
|
);
|
||||||
log::Level::Error
|
Off
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
crate::warn!("Invalid log level \"{level_str}\", ignoring");
|
crate::warn!("Invalid log level \"{level_str}\", ignoring");
|
||||||
|
@ -190,14 +162,17 @@ fn scope_alloc_from_scope_str(scope_str: &String) -> Option<ScopeAlloc> {
|
||||||
return Some(scope);
|
return Some(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct ScopeMap {
|
pub struct ScopeMap {
|
||||||
entries: Vec<ScopeMapEntry>,
|
entries: Vec<ScopeMapEntry>,
|
||||||
|
modules: Vec<(String, log::LevelFilter)>,
|
||||||
root_count: usize,
|
root_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct ScopeMapEntry {
|
pub struct ScopeMapEntry {
|
||||||
scope: String,
|
scope: String,
|
||||||
enabled: Option<log::Level>,
|
enabled: Option<log::LevelFilter>,
|
||||||
descendants: std::ops::Range<usize>,
|
descendants: std::ops::Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +191,8 @@ impl ScopeMap {
|
||||||
let mut items = Vec::with_capacity(
|
let mut items = Vec::with_capacity(
|
||||||
items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()),
|
items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()),
|
||||||
);
|
);
|
||||||
|
let mut modules = Vec::with_capacity(4);
|
||||||
|
|
||||||
if let Some(env_filter) = env_config {
|
if let Some(env_filter) = env_config {
|
||||||
// TODO: parse on load instead of every reload
|
// TODO: parse on load instead of every reload
|
||||||
items.extend(
|
items.extend(
|
||||||
|
@ -223,15 +200,17 @@ impl ScopeMap {
|
||||||
.directive_names
|
.directive_names
|
||||||
.iter()
|
.iter()
|
||||||
.zip(env_filter.directive_levels.iter())
|
.zip(env_filter.directive_levels.iter())
|
||||||
.filter_map(|(scope, level_filter)| {
|
.filter_map(|(scope_str, &level_filter)| {
|
||||||
if items_input_map.get(scope).is_some() {
|
if items_input_map.get(scope_str).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let scope = scope_alloc_from_scope_str(scope)?;
|
if scope_str.contains("::") {
|
||||||
// TODO: use level filters instead of scopes in scope map
|
modules.push((scope_str.clone(), level_filter));
|
||||||
let level = level_filter.to_level()?;
|
return None;
|
||||||
|
}
|
||||||
|
let scope = scope_alloc_from_scope_str(scope_str)?;
|
||||||
|
|
||||||
Some((scope, level))
|
Some((scope, level_filter))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -239,16 +218,22 @@ impl ScopeMap {
|
||||||
items_input_map
|
items_input_map
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(scope_str, level_str)| {
|
.filter_map(|(scope_str, level_str)| {
|
||||||
|
if scope_str.contains("::") {
|
||||||
|
modules.push((scope_str.clone(), level_str.parse().ok()?));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let level_filter = level_filter_from_str(&level_str)?;
|
||||||
let scope = scope_alloc_from_scope_str(&scope_str)?;
|
let scope = scope_alloc_from_scope_str(&scope_str)?;
|
||||||
let level = level_from_level_str(&level_str)?;
|
return Some((scope, level_filter));
|
||||||
return Some((scope, level));
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
items.sort_by(|a, b| a.0.cmp(&b.0));
|
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 {
|
let mut this = Self {
|
||||||
entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
|
entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
|
||||||
|
modules,
|
||||||
root_count: 0,
|
root_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -329,10 +314,15 @@ impl ScopeMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.entries.is_empty()
|
self.entries.is_empty() && self.modules.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_enabled<S>(&self, scope: &[S; SCOPE_DEPTH_MAX], level: log::Level) -> EnabledStatus
|
pub fn is_enabled<S>(
|
||||||
|
&self,
|
||||||
|
scope: &[S; SCOPE_DEPTH_MAX],
|
||||||
|
module_path: Option<&str>,
|
||||||
|
level: log::Level,
|
||||||
|
) -> EnabledStatus
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
@ -346,7 +336,6 @@ impl ScopeMap {
|
||||||
{
|
{
|
||||||
for entry in cur_range {
|
for entry in cur_range {
|
||||||
if entry.scope == scope[depth].as_ref() {
|
if entry.scope == scope[depth].as_ref() {
|
||||||
// note:
|
|
||||||
enabled = entry.enabled.or(enabled);
|
enabled = entry.enabled.or(enabled);
|
||||||
cur_range = &self.entries[entry.descendants.clone()];
|
cur_range = &self.entries[entry.descendants.clone()];
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
@ -356,13 +345,23 @@ impl ScopeMap {
|
||||||
break 'search;
|
break 'search;
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled.map_or(EnabledStatus::NotConfigured, |level_enabled| {
|
if enabled.is_none() && !self.modules.is_empty() && module_path.is_some() {
|
||||||
if level <= level_enabled {
|
let module_path = module_path.unwrap();
|
||||||
EnabledStatus::Enabled
|
for (module, filter) in &self.modules {
|
||||||
} else {
|
if module == module_path {
|
||||||
EnabledStatus::Disabled
|
enabled.replace(*filter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if let Some(enabled_filter) = enabled {
|
||||||
|
if level <= enabled_filter {
|
||||||
|
return EnabledStatus::Enabled;
|
||||||
|
}
|
||||||
|
return EnabledStatus::Disabled;
|
||||||
|
}
|
||||||
|
return EnabledStatus::NotConfigured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,63 +443,108 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
use log::Level;
|
use log::Level;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Trace),
|
map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Debug),
|
map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Debug),
|
map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Info),
|
map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Trace),
|
map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Info),
|
map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Info),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Warn),
|
map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Warn),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Debug),
|
map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Debug),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Warn),
|
map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Warn),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Error),
|
map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Error),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Info),
|
map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Info),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Error),
|
map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Error),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Warn),
|
map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Warn),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_enabled_module() {
|
||||||
|
let mut map = scope_map_from_keys(&[("a", "trace")]);
|
||||||
|
map.modules = [("a::b::c", "trace"), ("a::b::d", "debug")]
|
||||||
|
.map(|(k, v)| (k.to_string(), v.parse().unwrap()))
|
||||||
|
.to_vec();
|
||||||
|
use log::Level;
|
||||||
|
assert_eq!(
|
||||||
|
map.is_enabled(
|
||||||
|
&scope_from_scope_str("__unused__"),
|
||||||
|
Some("a::b::c"),
|
||||||
|
Level::Trace
|
||||||
|
),
|
||||||
|
EnabledStatus::Enabled
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
map.is_enabled(
|
||||||
|
&scope_from_scope_str("__unused__"),
|
||||||
|
Some("a::b::d"),
|
||||||
|
Level::Debug
|
||||||
|
),
|
||||||
|
EnabledStatus::Enabled
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
map.is_enabled(
|
||||||
|
&scope_from_scope_str("__unused__"),
|
||||||
|
Some("a::b::d"),
|
||||||
|
Level::Trace,
|
||||||
|
),
|
||||||
|
EnabledStatus::Disabled
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
map.is_enabled(
|
||||||
|
&scope_from_scope_str("__unused__"),
|
||||||
|
Some("a::e"),
|
||||||
|
Level::Info
|
||||||
|
),
|
||||||
|
EnabledStatus::NotConfigured
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
map.is_enabled(&scope_from_scope_str("a"), Some("a::b::d"), Level::Trace),
|
||||||
|
EnabledStatus::Enabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
|
fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
|
||||||
let hash_map: HashMap<String, String> = kv
|
let hash_map: HashMap<String, String> = kv
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -516,15 +560,15 @@ mod tests {
|
||||||
assert_eq!(map.root_count, 2);
|
assert_eq!(map.root_count, 2);
|
||||||
assert_eq!(map.entries.len(), 3);
|
assert_eq!(map.entries.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["a"]), log::Level::Debug),
|
map.is_enabled(&scope_new(&["a"]), None, log::Level::Debug),
|
||||||
EnabledStatus::NotConfigured
|
EnabledStatus::NotConfigured
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["a", "b"]), log::Level::Debug),
|
map.is_enabled(&scope_new(&["a", "b"]), None, log::Level::Debug),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["a", "b", "c"]), log::Level::Trace),
|
map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -548,20 +592,20 @@ mod tests {
|
||||||
assert_eq!(map.entries[4].scope, "q");
|
assert_eq!(map.entries[4].scope, "q");
|
||||||
assert_eq!(map.entries[5].scope, "u");
|
assert_eq!(map.entries[5].scope, "u");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["a", "b", "c", "d"]), log::Level::Trace),
|
map.is_enabled(&scope_new(&["a", "b", "c", "d"]), None, log::Level::Trace),
|
||||||
EnabledStatus::Enabled
|
EnabledStatus::Enabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["a", "b", "c"]), log::Level::Trace),
|
map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["u", "v"]), log::Level::Warn),
|
map.is_enabled(&scope_new(&["u", "v"]), None, log::Level::Warn),
|
||||||
EnabledStatus::Disabled
|
EnabledStatus::Disabled
|
||||||
);
|
);
|
||||||
// settings override env
|
// settings override env
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.is_enabled(&scope_new(&["e", "f", "g", "h"]), log::Level::Trace),
|
map.is_enabled(&scope_new(&["e", "f", "g", "h"]), None, log::Level::Trace),
|
||||||
EnabledStatus::Disabled,
|
EnabledStatus::Disabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,6 +257,7 @@ mod tests {
|
||||||
assert_eq!(size.load(Ordering::Relaxed), 0);
|
assert_eq!(size.load(Ordering::Relaxed), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test, ensuring that if log level values change we are made aware
|
||||||
#[test]
|
#[test]
|
||||||
fn test_log_level_names() {
|
fn test_log_level_names() {
|
||||||
assert_eq!(LEVEL_OUTPUT_STRINGS[log::Level::Error as usize], "ERROR");
|
assert_eq!(LEVEL_OUTPUT_STRINGS[log::Level::Error as usize], "ERROR");
|
||||||
|
|
|
@ -43,28 +43,28 @@ impl log::Log for Zlog {
|
||||||
if !self.enabled(record.metadata()) {
|
if !self.enabled(record.metadata()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let scope = match record.module_path_static() {
|
let (crate_name_scope, module_scope) = match record.module_path_static() {
|
||||||
Some(module_path) => {
|
Some(module_path) => {
|
||||||
// TODO: better module name -> scope translation
|
|
||||||
let crate_name = private::extract_crate_name_from_module_path(module_path);
|
let crate_name = private::extract_crate_name_from_module_path(module_path);
|
||||||
private::scope_new(&[crate_name])
|
let crate_name_scope = private::scope_new(&[crate_name]);
|
||||||
|
let module_scope = private::scope_new(&[module_path]);
|
||||||
|
(crate_name_scope, module_scope)
|
||||||
}
|
}
|
||||||
// TODO: when do we hit this
|
// TODO: when do we hit this
|
||||||
None => private::scope_new(&["*unknown*"]),
|
None => (private::scope_new(&[]), private::scope_new(&["*unknown*"])),
|
||||||
};
|
};
|
||||||
let level = record.metadata().level();
|
let level = record.metadata().level();
|
||||||
if !filter::is_scope_enabled(&scope, level) {
|
if !filter::is_scope_enabled(&crate_name_scope, record.module_path(), level) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sink::submit(sink::Record {
|
sink::submit(sink::Record {
|
||||||
scope,
|
scope: module_scope,
|
||||||
level,
|
level,
|
||||||
message: record.args(),
|
message: record.args(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) {
|
fn flush(&self) {
|
||||||
// todo: necessary?
|
|
||||||
sink::flush();
|
sink::flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ macro_rules! log {
|
||||||
($logger:expr, $level:expr, $($arg:tt)+) => {
|
($logger:expr, $level:expr, $($arg:tt)+) => {
|
||||||
let level = $level;
|
let level = $level;
|
||||||
let logger = $logger;
|
let logger = $logger;
|
||||||
let enabled = $crate::filter::is_scope_enabled(&logger.scope, level);
|
let enabled = $crate::filter::is_scope_enabled(&logger.scope, Some(module_path!()), level);
|
||||||
if enabled {
|
if enabled {
|
||||||
$crate::sink::submit($crate::sink::Record {
|
$crate::sink::submit($crate::sink::Record {
|
||||||
scope: logger.scope,
|
scope: logger.scope,
|
||||||
|
@ -143,7 +143,7 @@ macro_rules! error {
|
||||||
/// However, this is a feature not a bug, as it allows for a more accurate
|
/// However, this is a feature not a bug, as it allows for a more accurate
|
||||||
/// understanding of how long the action actually took to complete, including
|
/// understanding of how long the action actually took to complete, including
|
||||||
/// interruptions, which can help explain why something may have timed out,
|
/// interruptions, which can help explain why something may have timed out,
|
||||||
/// why it took longer to complete than it would had the await points resolved
|
/// why it took longer to complete than it would have had the await points resolved
|
||||||
/// immediately, etc.
|
/// immediately, etc.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! time {
|
macro_rules! time {
|
||||||
|
@ -168,15 +168,7 @@ macro_rules! scoped {
|
||||||
if index >= scope.len() {
|
if index >= scope.len() {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
panic!("Scope overflow trying to add scope {}", name);
|
unreachable!("Scope overflow trying to add scope... ignoring scope");
|
||||||
}
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
$crate::warn!(
|
|
||||||
parent =>
|
|
||||||
"Scope overflow trying to add scope {}... ignoring scope",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope[index] = name;
|
scope[index] = name;
|
||||||
|
@ -208,17 +200,31 @@ macro_rules! crate_name {
|
||||||
pub mod private {
|
pub mod private {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn extract_crate_name_from_module_path(module_path: &'static str) -> &'static str {
|
pub const fn extract_crate_name_from_module_path(module_path: &'static str) -> &'static str {
|
||||||
return module_path
|
let mut i = 0;
|
||||||
.split_once("::")
|
let mod_path_bytes = module_path.as_bytes();
|
||||||
.map(|(crate_name, _)| crate_name)
|
let mut index = mod_path_bytes.len();
|
||||||
.unwrap_or(module_path);
|
while i + 1 < mod_path_bytes.len() {
|
||||||
|
if mod_path_bytes[i] == b':' && mod_path_bytes[i + 1] == b':' {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
let Some((crate_name, _)) = module_path.split_at_checked(index) else {
|
||||||
|
return module_path;
|
||||||
|
};
|
||||||
|
return crate_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scope_new(scopes: &[&'static str]) -> Scope {
|
pub const fn scope_new(scopes: &[&'static str]) -> Scope {
|
||||||
assert!(scopes.len() <= SCOPE_DEPTH_MAX);
|
assert!(scopes.len() <= SCOPE_DEPTH_MAX);
|
||||||
let mut scope = [""; SCOPE_DEPTH_MAX];
|
let mut scope = [""; SCOPE_DEPTH_MAX];
|
||||||
scope[0..scopes.len()].copy_from_slice(scopes);
|
let mut i = 0;
|
||||||
|
while i < scopes.len() {
|
||||||
|
scope[i] = scopes[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
scope
|
scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +250,31 @@ pub struct Logger {
|
||||||
pub scope: Scope,
|
pub scope: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl log::Log for Logger {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
filter::is_possibly_enabled_level(metadata.level())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if !self.enabled(record.metadata()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let level = record.metadata().level();
|
||||||
|
if !filter::is_scope_enabled(&self.scope, record.module_path(), level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sink::submit(sink::Record {
|
||||||
|
scope: self.scope,
|
||||||
|
level,
|
||||||
|
message: record.args(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {
|
||||||
|
sink::flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
pub logger: Logger,
|
pub logger: Logger,
|
||||||
pub start_time: std::time::Instant,
|
pub start_time: std::time::Instant,
|
||||||
|
@ -269,6 +300,7 @@ impl Timer {
|
||||||
done: false,
|
done: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
|
pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
|
||||||
self.warn_if_longer_than = Some(warn_limit);
|
self.warn_if_longer_than = Some(warn_limit);
|
||||||
return self;
|
return self;
|
||||||
|
@ -313,5 +345,21 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crate_name() {
|
fn test_crate_name() {
|
||||||
assert_eq!(crate_name!(), "zlog");
|
assert_eq!(crate_name!(), "zlog");
|
||||||
|
assert_eq!(
|
||||||
|
private::extract_crate_name_from_module_path("my_speedy_⚡️_crate::some_module"),
|
||||||
|
"my_speedy_⚡️_crate"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
private::extract_crate_name_from_module_path("my_speedy_crate_⚡️::some_module"),
|
||||||
|
"my_speedy_crate_⚡️"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
private::extract_crate_name_from_module_path("my_speedy_crate_:⚡️:some_module"),
|
||||||
|
"my_speedy_crate_:⚡️:some_module"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
private::extract_crate_name_from_module_path("my_speedy_crate_::⚡️some_module"),
|
||||||
|
"my_speedy_crate_"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue