use env_filter::{Filter, ParseError};
use formatter::FormatFn;
use writer::{ConsoleWriter, InMemoryWriter, Logs, MultiWriter, Writer};
mod formatter;
#[cfg(feature = "service")]
pub mod service;
mod settings;
pub mod types;
pub mod writer;
use std::cell::RefCell;
use std::sync::Arc;
use arc_swap::{ArcSwap, ArcSwapAny};
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
#[allow(deprecated)]
pub use settings::LogSettings;
use crate::formatter::Formatter;
use crate::types::LogError;
pub struct Logger {
writer: Box<dyn Writer>,
filter: Arc<ArcSwapAny<Arc<Filter>>>,
format: FormatFn,
}
#[derive(Default)]
pub struct Builder {
filter: env_filter::Builder,
writer: MultiWriter,
format: formatter::Builder,
}
impl Builder {
pub fn new() -> Builder {
Default::default()
}
pub fn format_level(mut self, write: bool) -> Self {
self.format.format_level = write;
self
}
pub fn format_module_path(mut self, write: bool) -> Self {
self.format.format_module_path = write;
self
}
pub fn format_target(mut self, write: bool) -> Self {
self.format.format_target = write;
self
}
pub fn format_indent(mut self, indent: Option<usize>) -> Self {
self.format.format_indent = indent;
self
}
pub fn format_suffix(mut self, suffix: &'static str) -> Self {
self.format.format_suffix = suffix;
self
}
pub fn filter_module(mut self, module: &str, level: LevelFilter) -> Self {
self.filter.filter_module(module, level);
self
}
pub fn filter_level(mut self, level: LevelFilter) -> Self {
self.filter.filter_level(level);
self
}
pub fn filter(mut self, module: Option<&str>, level: LevelFilter) -> Self {
self.filter.filter(module, level);
self
}
pub fn try_parse_filters(mut self, filters: &str) -> Result<Self, ParseError> {
self.filter.try_parse(filters)?;
Ok(self)
}
pub fn add_writer(mut self, writer: Box<dyn Writer>) -> Self {
self.writer.add(writer);
self
}
pub fn try_init(self) -> Result<LoggerConfigHandle, SetLoggerError> {
let (logger, filter) = self.build();
let max_level = logger.filter();
log::set_boxed_logger(Box::new(logger))?;
log::set_max_level(max_level);
Ok(filter)
}
pub fn build(mut self) -> (Logger, LoggerConfigHandle) {
let filter = Arc::new(ArcSwap::from_pointee(self.filter.build()));
let writer: Box<dyn Writer> = if self.writer.writers.len() == 1 {
self.writer.writers.remove(0)
} else {
Box::new(self.writer)
};
(
Logger {
writer,
filter: filter.clone(),
format: self.format.build(),
},
LoggerConfigHandle { filter },
)
}
}
pub struct LoggerConfigHandle {
filter: Arc<ArcSwapAny<Arc<Filter>>>,
}
impl LoggerConfigHandle {
pub fn update_filters(&self, filters: &str) -> Result<(), LogError> {
let new_filter = env_filter::Builder::default().try_parse(filters)?.build();
let max_level = new_filter.filter();
self.filter.swap(Arc::new(new_filter));
log::set_max_level(max_level);
Ok(())
}
}
impl Logger {
pub fn filter(&self) -> LevelFilter {
self.filter.load().filter()
}
pub fn matches(&self, record: &Record) -> bool {
self.filter.load().matches(record)
}
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
self.filter.load().enabled(metadata)
}
fn log(&self, record: &Record) {
if self.matches(record) {
thread_local! {
static FORMATTER: RefCell<Formatter> = RefCell::new(Formatter::default());
}
let print = |formatter: &mut Formatter, record: &Record| {
let _ = (self.format)(formatter, record)
.and_then(|_| formatter.print(self.writer.as_ref()));
formatter.clear();
};
let printed = FORMATTER
.try_with(|tl_buf| {
match tl_buf.try_borrow_mut() {
Ok(ref mut formatter) => print(formatter, record),
Err(_) => {
print(&mut Formatter::default(), record);
}
}
})
.is_ok();
if !printed {
print(&mut Formatter::default(), record);
}
}
}
fn flush(&self) {}
}
mod std_fmt_impls {
use std::fmt;
use super::*;
impl fmt::Debug for Logger {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Logger")
.field("filter", &self.filter)
.finish()
}
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Logger")
.field("filter", &self.filter)
.finish()
}
}
}
pub fn init_log(settings: &LogSettings) -> Result<LoggerConfigHandle, LogError> {
let mut builder = Builder::default().try_parse_filters(&settings.log_filter)?;
if settings.enable_console {
builder = builder.add_writer(Box::new(ConsoleWriter::default()));
}
writer::InMemoryWriter::init_buffer(settings.in_memory_records, settings.max_record_length);
builder = builder.add_writer(Box::new(InMemoryWriter {}));
let config = builder.try_init()?;
Ok(config)
}
pub fn take_memory_records(max_count: usize, from_offset: usize) -> Logs {
writer::InMemoryWriter::take_records(max_count, from_offset)
}
#[cfg(test)]
mod tests {
use log::*;
use super::*;
#[test]
fn update_filter_at_runtime() {
let config = init_log(&LogSettings {
enable_console: true,
in_memory_records: 0,
max_record_length: 1024,
log_filter: "debug".to_string(),
})
.unwrap();
debug!("This one should be printed");
info!("This one should be printed");
config.update_filters("error").unwrap();
debug!("This one should NOT be printed");
info!("This one should NOT be printed");
config.update_filters("info").unwrap();
debug!("This one should NOT be printed");
info!("This one should be printed");
}
}