use log4rs::append::console::Target;
use log4rs::config::*;
use log4rs::encode::pattern::PatternEncoder;
use std::boxed::Box;
use std::fmt;
use std::io;
use std::path;
pub type LogHandle = log4rs::Handle;
pub enum LogConfig {
ConsoleLog(Target, log::LevelFilter),
FileLog(path::PathBuf, log::LevelFilter),
}
fn clone_target(target: &Target) -> Target {
match target {
Target::Stdout => Target::Stdout,
Target::Stderr => Target::Stderr,
}
}
impl LogConfig {
fn name(&self) -> &str {
match self {
LogConfig::ConsoleLog(target, _) => match target {
Target::Stdout => "STDOUT",
Target::Stderr => "STDERR",
},
LogConfig::FileLog(path, _) => path.to_str().expect("malformed UTF-8 in path name"),
}
}
fn level(&self) -> log::LevelFilter {
match self {
LogConfig::ConsoleLog(_, level) => *level,
LogConfig::FileLog(_, level) => *level,
}
}
fn filter(&self) -> Box<dyn log4rs::filter::Filter> {
Box::new(log4rs::filter::threshold::ThresholdFilter::new(
self.level(),
))
}
fn append(&self, pattern: Option<&str>) -> io::Result<Box<dyn log4rs::append::Append>> {
match self {
LogConfig::ConsoleLog(target, _) => Ok(Box::new(
log4rs::append::console::ConsoleAppender::builder()
.target(clone_target(target))
.encoder(Box::new(PatternEncoder::new(
pattern.unwrap_or("{d} {l} {t} - {m}{n}"),
)))
.build(),
)),
LogConfig::FileLog(path, _) => Ok(Box::new(
log4rs::append::file::FileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
pattern.unwrap_or("{d} {l} {t} - {m}{n}"),
)))
.build(path)?,
)),
}
}
fn appender(&self, pattern: Option<&str>) -> io::Result<Appender> {
Ok(Appender::builder()
.filter(self.filter())
.build(self.name(), self.append(pattern)?))
}
}
impl fmt::Debug for LogConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogConfig::ConsoleLog(Target::Stdout, level) => {
write!(f, "ConsoleLog(Stdout, {:?})", level)
}
LogConfig::ConsoleLog(Target::Stderr, level) => {
write!(f, "ConsoleLog(Stderr, {:?})", level)
}
LogConfig::FileLog(path, level) => write!(f, "FileLog({:?}, {:?})", path, level),
}
}
}
impl Clone for LogConfig {
fn clone(&self) -> LogConfig {
match self {
LogConfig::ConsoleLog(target, filter) => {
LogConfig::ConsoleLog(clone_target(target), *filter)
}
LogConfig::FileLog(path, filter) => LogConfig::FileLog(path.clone(), *filter),
}
}
}
fn build_config<'a, I: IntoIterator<Item = &'a LogConfig>>(
cfgs: I,
pattern: Option<&str>,
) -> io::Result<Config> {
let mut config_builder = Config::builder();
let mut root_builder = Root::builder();
for cfg in cfgs.into_iter() {
config_builder = config_builder.appender(cfg.appender(pattern)?);
root_builder = root_builder.appender(cfg.name());
}
let root = root_builder.build(log::LevelFilter::Trace);
Ok(config_builder
.build(root)
.expect("While building log config"))
}
pub fn init_logging<'a, I: IntoIterator<Item = &'a LogConfig>>(
cfgs: I,
pattern: Option<&str>,
) -> io::Result<LogHandle> {
Ok(log4rs::init_config(build_config(cfgs, pattern)?).expect("While initializing logging"))
}
pub fn reinit_logging<'a, I: IntoIterator<Item = &'a LogConfig>>(
cfgs: I,
pattern: Option<&str>,
handle: &LogHandle,
) -> io::Result<()> {
handle.set_config(build_config(cfgs, pattern)?);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use log::info;
use std::fs;
#[allow(dead_code)]
fn log_rotation() {
let tempdir = tempfile::TempDir::new().unwrap();
let mut logpath = path::PathBuf::from(tempdir.path());
let mut logpath_rotated = logpath.clone();
logpath.push("logfile");
logpath_rotated.push("logfile.old");
let logconfig = vec![LogConfig::FileLog(logpath.clone(), log::LevelFilter::Info)];
let handle = init_logging(&logconfig, None).unwrap();
info!("PRE-ROTATION");
fs::rename(&logpath, &logpath_rotated).unwrap();
info!("MID-ROTATION");
reinit_logging(&logconfig, None, &handle).unwrap();
info!("POST-ROTATION");
}
}