#![doc = include_utils::include_md!("README.md:description")]
#![doc = include_utils::include_md!("README.md:basic_example")]
#![doc = include_utils::include_md!("README.md:async_example")]
use std::borrow::Cow;
use stack::ContextRecords;
use self::stack::CONTEXT_STACK;
pub use self::{context::LogContext, future::FutureExt, value::ContextValue};
mod context;
pub mod future;
pub mod guard;
mod stack;
mod value;
type StaticCowStr = Cow<'static, str>;
pub struct ContextLogger {
default_records: ContextRecords,
inner: Box<dyn log::Log>,
}
impl ContextLogger {
pub fn new<L>(inner: L) -> Self
where
L: log::Log + 'static,
{
Self {
default_records: ContextRecords::new(),
inner: Box::new(inner),
}
}
pub fn init(self, max_level: log::LevelFilter) {
self.try_init(max_level)
.expect("ContextLogger::init should not be called after logger initialization");
}
pub fn try_init(self, max_level: log::LevelFilter) -> Result<(), log::SetLoggerError> {
log::set_max_level(max_level);
log::set_boxed_logger(Box::new(self))
}
#[must_use]
pub fn default_record(
mut self,
key: impl Into<StaticCowStr>,
value: impl Into<ContextValue>,
) -> Self {
self.default_records.push((key.into(), value.into()));
self
}
}
impl std::fmt::Debug for ContextLogger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContextLogger").finish_non_exhaustive()
}
}
impl log::Log for ContextLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.inner.enabled(metadata)
}
fn log(&self, record: &log::Record) {
let error = CONTEXT_STACK.try_with(|stack| {
if let Some(top) = stack.top() {
let extra_records = ExtraRecords {
source: &record.key_values(),
default_records: self.default_records.as_slice(),
context_records: top.as_slice(),
};
self.inner
.log(&record.to_builder().key_values(&extra_records).build());
} else {
let extra_records = ExtraRecords {
source: &record.key_values(),
default_records: self.default_records.as_slice(),
context_records: &[],
};
self.inner
.log(&record.to_builder().key_values(&extra_records).build());
}
});
if let Err(err) = error {
self.inner.log(record);
eprintln!("Error accessing context stack: {err}");
}
}
fn flush(&self) {
self.inner.flush();
}
}
struct ExtraRecords<'a, I> {
source: &'a dyn log::kv::Source,
default_records: I,
context_records: I,
}
impl<'a, I> log::kv::Source for ExtraRecords<'a, I>
where
I: IntoIterator<Item = &'a (StaticCowStr, ContextValue)> + Copy,
{
fn visit<'kvs>(
&'kvs self,
visitor: &mut dyn log::kv::VisitSource<'kvs>,
) -> Result<(), log::kv::Error> {
let all_records = self.default_records.into_iter().chain(self.context_records);
for (key, value) in all_records {
visitor.visit_pair(log::kv::Key::from_str(key), value.as_log_value())?;
}
self.source.visit(visitor)
}
}