#![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, collections::HashMap};
use crate::records::LogRecordRef;
mod context;
pub mod future;
mod records;
mod scope;
mod value;
type LogValueFn = Box<dyn Fn(&log::Record) -> LogValue + Send + Sync>;
pub use self::{
context::LogContext,
future::FutureExt,
records::LogRecords,
scope::{LogContextExt, LogScope},
value::LogValue,
};
pub struct ContextLogger {
inner: Box<dyn log::Log>,
default_records: LogRecords,
dynamic_default_records: HashMap<Cow<'static, str>, LogValueFn>,
}
impl ContextLogger {
pub fn new<L>(inner: L) -> Self
where
L: log::Log + 'static,
{
Self {
inner: Box::new(inner),
default_records: LogRecords::new(),
dynamic_default_records: HashMap::new(),
}
}
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 with_default_record(
mut self,
key: impl Into<Cow<'static, str>>,
value: impl Into<LogValue>,
) -> Self {
self.default_records.insert(key, value);
self
}
#[must_use]
pub fn with_default_record_fn<V: Into<LogValue>>(
mut self,
key: impl Into<Cow<'static, str>>,
f: impl Fn(&log::Record) -> V + Send + Sync + 'static,
) -> Self {
self.dynamic_default_records
.insert(key.into(), Box::new(move |record| f(record).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) {
if !self.enabled(record.metadata()) {
return;
}
let error = scope::stack::SCOPE_STACK.try_with(|stack| {
let dynamic_default_records = self
.dynamic_default_records
.iter()
.map(|(key, f)| (key, f(record)))
.collect::<Vec<_>>();
let default_records = self
.default_records
.iter()
.chain(dynamic_default_records.iter().map(|(k, v)| (*k, v)));
if let Some(top) = stack.top() {
self.inner.log(
&record
.to_builder()
.key_values(&SourceWithRecords {
source: &record.key_values(),
records: default_records.chain(top.records()),
})
.build(),
);
} else {
self.inner.log(
&record
.to_builder()
.key_values(&SourceWithRecords {
source: &record.key_values(),
records: default_records,
})
.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 {}
}