use std::cell::RefCell;
use std::io::{stderr, stdout};
use anyhow::anyhow;
use log::{LevelFilter as LogLevelFilter, LevelFilter};
use tracing_log::AsTrace;
use tracing_subscriber::fmt::writer::{BoxMakeWriter, MakeWriterExt};
use tracing_subscriber::FmtSubscriber;
use tracing_subscriber::util::SubscriberInitExt;
use crate::log::sink::Sink;
thread_local! {
pub(crate) static LOGGER: RefCell<Vec<(String, LogLevelFilter)>> = RefCell::new(vec![]);
}
pub(crate) fn init_logger() {
LOGGER.with(|logger| {
*logger.borrow_mut() = vec![];
});
}
pub(crate) fn add_sink(sink_specifier: &str, level_filter: LogLevelFilter) -> anyhow::Result<()> {
LOGGER.with(|logger_data| {
let mut logger_inner = logger_data.borrow_mut();
logger_inner.push((sink_specifier.to_string(), level_filter.clone()));
Ok(())
})
}
pub(crate) fn apply_logger() -> anyhow::Result<()> {
LOGGER.with(|logger| {
let mut logger_inner = logger.borrow_mut();
let max_level = logger_inner.iter()
.max_by(|a, b| a.1.cmp(&b.1))
.map(|l| l.1)
.unwrap_or(LogLevelFilter::Info);
let subscriber_builder = FmtSubscriber::builder()
.with_max_level(max_level.as_trace())
.with_thread_names(true)
.with_ansi(false) ;
let subscriber = if let Some((sink, level)) = logger_inner.first() {
let initial_writer = sink_to_make_writer(sink.as_str(), level);
let writer = logger_inner.iter().skip(1).fold(initial_writer, |acc, (s, l)| {
BoxMakeWriter::new(acc.and(sink_to_make_writer(s.as_str(), l)))
});
subscriber_builder.with_writer(writer).finish()
} else {
subscriber_builder.with_writer(BoxMakeWriter::new(stdout)).finish()
};
logger_inner.clear();
subscriber.try_init().map_err(|err| anyhow!(err))
})
}
fn sink_to_make_writer(sink: &str, level: &LevelFilter) -> BoxMakeWriter {
let lvl = level.as_trace().into_level();
match Sink::try_from(sink).unwrap() {
Sink::Stdout(_) => {
if let Some(lvl) = lvl {
BoxMakeWriter::new(stdout.with_max_level(lvl))
} else {
BoxMakeWriter::new(stdout.with_filter(|_| false))
}
},
Sink::Stderr(_) => {
if let Some(lvl) = lvl {
BoxMakeWriter::new(stderr.with_max_level(lvl))
} else {
BoxMakeWriter::new(stderr.with_filter(|_| false))
}
},
Sink::File(f) => {
if let Some(lvl) = lvl {
BoxMakeWriter::new(f.with_max_level(lvl))
} else {
BoxMakeWriter::new(f.with_filter(|_| false))
}
},
Sink::Buffer(b) => {
if let Some(lvl) = lvl {
BoxMakeWriter::new(b.with_max_level(lvl))
} else {
BoxMakeWriter::new(b.with_filter(|_| false))
}
}
}
}
#[cfg(test)]
mod tests {
use log::LevelFilter;
use tempfile::tempdir;
use crate::log::logger::sink_to_make_writer;
#[test]
fn sink_to_make_writer_with_level_off() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.log");
sink_to_make_writer("stdout", &LevelFilter::Off);
sink_to_make_writer("stderr", &LevelFilter::Off);
sink_to_make_writer("buffer", &LevelFilter::Off);
let s = format!("file {}", file_path.to_string_lossy());
sink_to_make_writer(s.as_str(), &LevelFilter::Off);
}
}