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:
Ben Kunkle 2025-04-21 11:43:24 -04:00 committed by GitHub
parent f8ac6eef75
commit d13cd007a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 211 additions and 118 deletions

View file

@ -43,28 +43,28 @@ impl log::Log for Zlog {
if !self.enabled(record.metadata()) {
return;
}
let scope = match record.module_path_static() {
let (crate_name_scope, module_scope) = match record.module_path_static() {
Some(module_path) => {
// TODO: better module name -> scope translation
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
None => private::scope_new(&["*unknown*"]),
None => (private::scope_new(&[]), private::scope_new(&["*unknown*"])),
};
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;
}
sink::submit(sink::Record {
scope,
scope: module_scope,
level,
message: record.args(),
});
}
fn flush(&self) {
// todo: necessary?
sink::flush();
}
}
@ -74,7 +74,7 @@ macro_rules! log {
($logger:expr, $level:expr, $($arg:tt)+) => {
let level = $level;
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 {
$crate::sink::submit($crate::sink::Record {
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
/// understanding of how long the action actually took to complete, including
/// 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.
#[macro_export]
macro_rules! time {
@ -168,15 +168,7 @@ macro_rules! scoped {
if index >= scope.len() {
#[cfg(debug_assertions)]
{
panic!("Scope overflow trying to add scope {}", name);
}
#[cfg(not(debug_assertions))]
{
$crate::warn!(
parent =>
"Scope overflow trying to add scope {}... ignoring scope",
name
);
unreachable!("Scope overflow trying to add scope... ignoring scope");
}
}
scope[index] = name;
@ -208,17 +200,31 @@ macro_rules! crate_name {
pub mod private {
use super::*;
pub fn extract_crate_name_from_module_path(module_path: &'static str) -> &'static str {
return module_path
.split_once("::")
.map(|(crate_name, _)| crate_name)
.unwrap_or(module_path);
pub const fn extract_crate_name_from_module_path(module_path: &'static str) -> &'static str {
let mut i = 0;
let mod_path_bytes = module_path.as_bytes();
let mut index = mod_path_bytes.len();
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);
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
}
@ -244,6 +250,31 @@ pub struct Logger {
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 logger: Logger,
pub start_time: std::time::Instant,
@ -269,6 +300,7 @@ impl Timer {
done: false,
};
}
pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
self.warn_if_longer_than = Some(warn_limit);
return self;
@ -313,5 +345,21 @@ mod tests {
#[test]
fn test_crate_name() {
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_"
);
}
}