#![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 crate::records::LogRecordRef;
mod context;
pub mod future;
mod records;
mod scope;
mod value;
pub use self::{
context::LogContext,
future::FutureExt,
records::LogRecords,
scope::{LogContextExt, LogScope},
value::LogValue,
};
pub struct ContextLogger {
records: LogRecords,
inner: Box<dyn log::Log>,
}
impl ContextLogger {
pub fn new<L>(inner: L) -> Self
where
L: log::Log + 'static,
{
Self {
records: LogRecords::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.insert(key, value);
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::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 = LogRecordRef<'a>> + Clone,
{
fn visit<'kvs>(
&'kvs self,
visitor: &mut dyn log::kv::VisitSource<'kvs>,
) -> Result<(), log::kv::Error> {
for (key, value) in self.records.clone() {
visitor.visit_pair(log::kv::Key::from_str(key), value.as_log_value())?;
}
self.source.visit(visitor)
}
}
mod private {
pub trait Sealed {}
impl<F: Future> Sealed for F {}
impl Sealed for crate::LogContext {}
}