use std::{fs::OpenOptions, io::Write};
use log::*;
use simplelog::*;
use time::{format_description, OffsetDateTime};
use crate::{
error::{DepuraError, DepuraResult as Result},
logger::Logger,
};
#[derive(Clone, Debug)]
pub struct MultiLogger {
loggers: Vec<Logger>,
level: Option<LevelFilter>,
target_level: Option<LevelFilter>,
allowed: Vec<&'static str>,
ignored: Vec<&'static str>,
datetime_fmt: Vec<FormatItem<'static>>,
}
impl Default for MultiLogger {
fn default() -> Self {
Self {
loggers: vec![],
datetime_fmt: format_description::parse(Logger::DATETIME_FMT).unwrap(),
level: None,
target_level: None,
allowed: vec![],
ignored: vec![],
}
}
}
impl MultiLogger {
pub fn new() -> Self {
Self::default()
}
pub fn init(self) -> Result<()> {
let hsepline = format!["# {}\n", "-".repeat(80)];
let hdatetime = format![
"# {}\n",
OffsetDateTime::now_local()
.expect("couldn't fetch current local datetime")
.format(&self.datetime_fmt)
.expect("couldn't format current datetime")
];
let mut inner_loggers: Vec<Box<dyn SharedLogger>> = vec![];
if self.loggers.is_empty() {
return Err(DepuraError::NoLoggers);
}
for mut logger in self.loggers {
let level = self.level.unwrap_or(logger.level);
let target_level = self.target_level.unwrap_or(logger.target_level);
logger.config.set_target_level(target_level);
let allowed = if self.allowed.is_empty() {
logger.allowed
} else {
self.allowed.clone()
};
let ignored = if self.ignored.is_empty() {
logger.ignored
} else {
self.ignored.clone()
};
for a in allowed.iter() {
logger.config.add_filter_allow_str(a);
}
for i in ignored.iter() {
logger.config.add_filter_ignore_str(i);
}
let config = logger.config.build();
let hallowed = if allowed.is_empty() {
String::new()
} else {
format!["# allowed: \"{}\".\n", allowed.join("\", \"")]
};
let hignored = if ignored.is_empty() {
String::new()
} else {
format!["# ignored: \"{}\".\n", ignored.join("\", \"")]
};
let hprefix = format![
"{0}# Starting log: \"{1}\"\n{hdatetime}{2}{3}",
hsepline, logger.name, hallowed, hignored,
];
if let Some(file_path) = logger.file_path {
let mut file = OpenOptions::new();
file.write(true)
.create(logger.file_create)
.append(logger.file_append);
let mut file = file.open(file_path).expect("creating logfile");
if !logger.file_append && logger.file_truncate {
file.set_len(0).expect("set_len(0)")
}
file.write_fmt(format_args!["{hprefix}{hsepline}"])
.unwrap_or_else(|_| panic!["couldn't write header to «{file:?}»"]);
inner_loggers.push(WriteLogger::new(level, config, file));
} else {
print!("{hprefix}{hsepline}");
inner_loggers.push(SimpleLogger::new(level, config));
}
}
CombinedLogger::init(inner_loggers).map_err(|e| e.into())
}
pub fn include(mut self, logger: Logger) -> Self {
self.loggers.push(logger);
self
}
pub fn datetime_format(mut self, format: &'static str) -> Result<Self> {
self.datetime_fmt = format_description::parse(format)?;
Ok(self)
}
pub fn level(mut self, level: impl Into<Option<LevelFilter>>) -> Self {
self.level = level.into();
self
}
pub fn target_level(mut self, target_level: impl Into<Option<LevelFilter>>) -> Self {
self.target_level = target_level.into();
self
}
pub fn target_level_all(mut self) -> Self {
self.target_level = LevelFilter::Error.into();
self
}
pub fn allow(mut self, allow: &'static str) -> Self {
self.allowed.push(allow);
self
}
pub fn ignore(mut self, ignore: &'static str) -> Self {
self.ignored.push(ignore);
self
}
}