#![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;
pub use self::{context::LogContext, future::FutureExt, scope::LogScope, value::LogValue};
use crate::{record::LogRecord, stack::SCOPE_STACK};
mod context;
pub mod future;
mod record;
mod scope;
mod stack;
mod value;
pub struct ContextLogger {
records: Vec<LogRecord>,
inner: Box<dyn log::Log>,
}
impl ContextLogger {
pub fn new<L>(inner: L) -> Self
where
L: log::Log + 'static,
{
Self {
records: Vec::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<Cow<'static, str>>,
value: impl Into<LogValue>,
) -> Self {
self.records.push((key, 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 = SCOPE_STACK.try_with(|stack| {
if let Some(top) = stack.top() {
self.inner.log(
&record
.to_builder()
.key_values(&SourceWithRecords {
source: &record.key_values(),
records: self.records.iter().chain(top.records()),
})
.build(),
);
} else {
self.inner.log(
&record
.to_builder()
.key_values(&SourceWithRecords {
source: &record.key_values(),
records: self.records.iter(),
})
.build(),
);
}
});
if let Err(err) = error {
self.inner.log(record);
eprintln!("Error accessing context stack: {err}");
}
}
fn flush(&self) {
self.inner.flush();
}
}
struct SourceWithRecords<'a, I> {
source: &'a dyn log::kv::Source,
records: I,
}
impl<'a, I> log::kv::Source for SourceWithRecords<'a, I>
where
I: Iterator<Item = &'a LogRecord> + Clone,
{
fn visit<'kvs>(
&'kvs self,
visitor: &mut dyn log::kv::VisitSource<'kvs>,
) -> Result<(), log::kv::Error> {
for record in self.records.clone() {
visitor.visit_pair(
log::kv::Key::from_str(record.key()),
record.value().as_log_value(),
)?;
}
self.source.visit(visitor)
}
}