#![doc = include_str!("../README.md")]
#![feature(local_key_cell_methods)]
pub struct LogWrap {
pub enabled: Box<dyn Fn(&dyn log::Log, &log::Metadata) -> bool + Send + Sync>,
pub log: Box<dyn Fn(&dyn log::Log, &log::Record) + Send + Sync>,
pub logger: Box<dyn log::Log>,
}
impl log::Log for LogWrap {
fn enabled(&self, metadata: &log::Metadata) -> bool {
(self.enabled)(self.logger.as_ref(), metadata)
}
fn log(&self, record: &log::Record) {
(self.log)(self.logger.as_ref(), record)
}
fn flush(&self) {
self.logger.flush()
}
}
impl From<Box<dyn log::Log>> for LogWrap {
fn from(value: Box<dyn log::Log>) -> Self {
Self::new(value)
}
}
impl LogWrap {
pub fn new(logger: Box<dyn log::Log>) -> Self {
Self {
enabled: Box::new(|prev, metadata| prev.enabled(metadata)),
log: Box::new(|prev, record| prev.log(record)),
logger,
}
}
#[cfg(feature = "std")]
pub fn init(self) -> Result<(), log::SetLoggerError> {
log::set_boxed_logger(Box::new(self))
}
#[cfg(feature = "std")]
pub fn init_with_default_level(self) -> Result<(), log::SetLoggerError> {
log::set_max_level(if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
});
self.init()
}
pub fn log(self, f: impl Fn(&dyn log::Log, &log::Record) + Send + Sync + 'static) -> Self {
Self {
log: Box::new(f),
..self
}
}
pub fn chain(self, f: impl Fn(&dyn log::Log, &log::Record) + Send + Sync + 'static) -> Self {
let prev = self.log;
Self {
log: Box::new(move |l, r| {
prev(l, r);
f(l, r);
}),
..self
}
}
pub fn filter(
self,
f: impl Fn(&dyn log::Log, &log::Record) -> bool + Send + Sync + 'static,
) -> Self {
let prev = self.log;
Self {
log: Box::new(move |l, r| {
if f(l, r) {
prev(l, r);
}
}),
..self
}
}
pub fn enabled(
self,
f: impl Fn(&dyn log::Log, &log::Metadata) -> bool + Send + Sync + 'static,
) -> Self {
Self {
enabled: Box::new(f),
..self
}
}
}
impl LogWrap {
pub fn black_module(self, mods: impl IntoIterator<Item = impl Into<String>>) -> Self {
let mods = mods.into_iter().map(Into::into).collect::<Vec<_>>();
self.filter(move |_, record| {
if record
.module_path()
.or(record.module_path_static())
.filter(|m| mods.iter().any(|s| m.starts_with(s)))
.is_some()
{
return false;
}
true
})
}
pub fn enable_thread_capture(self) -> Self {
self.log(move |logger, record| {
if ENABLED.get() {
CAPTURED.with_borrow_mut(|v| v.push(LogInfo::from(record)));
return;
}
logger.log(record);
})
}
}
static MUTEX: Mutex<()> = Mutex::new(());
pub struct CaptureGuard;
impl Drop for CaptureGuard {
fn drop(&mut self) {
ENABLED.set(false);
let _guard = MUTEX.lock().unwrap();
let logger = log::logger();
for item in CAPTURED.take() {
logger.log(
&log::RecordBuilder::new()
.args(format_args!("{}", item.text))
.module_path(item.module.as_ref().map(AsRef::as_ref))
.file(item.file.as_ref().map(AsRef::as_ref))
.level(item.level)
.line((item.line > 0).then_some(item.line))
.build(),
);
}
}
}
pub fn capture_thread_log() -> CaptureGuard {
ENABLED.set(true);
CaptureGuard
}
#[derive(Debug, Clone, PartialEq)]
pub struct LogInfo {
pub line: u32,
pub level: log::Level,
pub text: String,
pub file: Option<Box<str>>,
pub module: Option<Box<str>>,
}
impl From<&log::Record<'_>> for LogInfo {
fn from(record: &log::Record) -> Self {
LogInfo {
line: record.line().unwrap_or(0),
level: record.level(),
file: record.file().or(record.file_static()).map(Into::into),
text: record.args().to_string(),
module: record
.module_path()
.or(record.module_path_static())
.map(Into::into),
}
}
}
use std::{
cell::{Cell, RefCell},
sync::Mutex,
};
thread_local! {
static CAPTURED: RefCell<Vec<LogInfo>> = RefCell::new(vec![]);
static ENABLED: Cell<bool> = Cell::new(false);
}