-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
513401b
commit 2f80d09
Showing
4 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "examples_common" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[[example]] | ||
name = "example_logging" | ||
path = "examples/example_logging.rs" | ||
|
||
[dependencies] | ||
parking_lot.workspace = true | ||
tracing.workspace = true | ||
tracing-subscriber.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use tracing::Level; | ||
|
||
use examples_common::logging::LogLevelFilter; | ||
|
||
fn main() { | ||
// setup logging | ||
examples_common::logging::init_logging(LogLevelFilter::builder() | ||
.global(Level::WARN) | ||
// set the logging for this executable | ||
.level(env!("CARGO_CRATE_NAME"), Level::TRACE) | ||
// set the logging for the examples_common crate | ||
.level(examples_common::CRATE_NAME, Level::TRACE) | ||
.build() | ||
); | ||
|
||
// test the crate | ||
examples_common::logging::self_log_test(); | ||
|
||
// test here | ||
examples_common::logging::log_level_test!(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod logging; | ||
|
||
/// The name of this crate, exported as a utility for logging. | ||
pub const CRATE_NAME: &str = env!("CARGO_CRATE_NAME"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
//! Logging utilities. | ||
use std::collections::HashMap; | ||
use std::io; | ||
use std::iter::successors; | ||
use std::sync::Arc; | ||
|
||
use tracing::{Level, Metadata}; | ||
use tracing_subscriber::{filter, Layer, Registry}; | ||
use tracing_subscriber::layer::SubscriberExt; | ||
|
||
use parking_lot::Once; | ||
|
||
const DEFAULT_LEVEL: Level = Level::WARN; | ||
const DEFAULT_CRATE_LEVEL: Level = Level::DEBUG; | ||
|
||
static LOGGING_INIT: Once = Once::new(); | ||
|
||
/// Emit logs at all levels to test logging. | ||
#[macro_export] | ||
macro_rules! log_level_test { | ||
() => {{ | ||
tracing::trace!("TRACE"); | ||
tracing::debug!("DEBUG"); | ||
tracing::info!("INFO"); | ||
tracing::warn!("WARN"); | ||
tracing::error!("ERROR"); | ||
}}; | ||
} | ||
|
||
pub use log_level_test; | ||
|
||
/// Initialize logging idempotently. | ||
/// | ||
/// Calling this more than once will have no effect. | ||
pub fn init_logging(filter: LogLevelFilter) { | ||
let filter = Arc::new(filter); | ||
|
||
LOGGING_INIT.call_once(move || init_logging_actual(filter)); | ||
} | ||
|
||
/// Test logging in this crate by emitting events at all log levels. | ||
#[allow(unused)] | ||
pub fn self_log_test() { | ||
log_level_test!(); | ||
} | ||
|
||
fn init_logging_actual(filter: Arc<LogLevelFilter>) { | ||
tracing::subscriber::set_global_default( | ||
Registry::default().with( | ||
tracing_subscriber::fmt::layer() | ||
.with_writer(io::stderr) | ||
.pretty() | ||
.with_filter(filter::filter_fn(move |meta| filter.allow(meta))), | ||
), | ||
) | ||
.unwrap(); | ||
} | ||
|
||
pub struct LogLevelFilter { | ||
global: Level, | ||
modules: HashMap<String, Level>, | ||
} | ||
|
||
impl Default for LogLevelFilter { | ||
fn default() -> Self { | ||
Self::builder().build() | ||
} | ||
} | ||
|
||
impl LogLevelFilter { | ||
pub fn builder() -> LogLevelFilterBuilder { | ||
let mut b = LogLevelFilterBuilder { | ||
..Default::default() | ||
}; | ||
|
||
b.global = Some(DEFAULT_LEVEL); | ||
b.modules | ||
.insert(env!("CARGO_CRATE_NAME").to_string(), DEFAULT_CRATE_LEVEL); | ||
|
||
b | ||
} | ||
|
||
pub fn set_global(&mut self, level: Level) { | ||
self.global = level; | ||
} | ||
|
||
pub fn filter<S>(&mut self, logger: S, level: Level) | ||
where | ||
S: Into<String>, | ||
{ | ||
self.modules.insert(logger.into(), level); | ||
} | ||
|
||
pub fn allow(&self, meta: &Metadata) -> bool { | ||
// NOTE on levels: trace has the _lowest_ possible value in sorting (i.e. 0), while error | ||
// has the *highest* possible value in sorting (i.e. 4). thus, in order for us to | ||
// determine whether a given level is allowed, we must check `log.level` is greater | ||
// than or equal to `log_rule.level`. | ||
if let Some(module) = meta.module_path() { | ||
let level = successors(Some(module), |m| { | ||
m.rsplit_once("::").map(|(head, _tail)| head) | ||
}) | ||
.find_map(|m| self.modules.get(m)) | ||
.unwrap_or(&self.global); | ||
|
||
return meta.level() <= level; | ||
} | ||
|
||
true | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct LogLevelFilterBuilder { | ||
global: Option<Level>, | ||
#[allow(unused)] | ||
modules: HashMap<String, Level>, | ||
} | ||
|
||
impl LogLevelFilterBuilder { | ||
#[allow(unused)] | ||
pub fn global(mut self, level: Level) -> Self { | ||
self.global = level.into(); | ||
self | ||
} | ||
|
||
pub fn level<S>(mut self, logger: S, level: Level) -> Self | ||
where | ||
S: Into<String>, | ||
{ | ||
self.modules.insert(logger.into(), level); | ||
self | ||
} | ||
|
||
pub fn build(self) -> LogLevelFilter { | ||
LogLevelFilter { | ||
global: self.global.unwrap_or(DEFAULT_LEVEL), | ||
modules: self.modules, | ||
} | ||
} | ||
} |