use flexi_logger::{
Age, Cleanup, Criterion, DeferredNow, FileSpec, Logger as FlexiLogger, Naming, WriteMode,
};
use crate::config::LoggerConfig;
pub struct Logger;
impl Logger {
pub fn init() {
Self::init_with_config(LoggerConfig::default()).expect("Logger init failed");
}
pub fn init_with_level(level: String) {
let level = level
.parse::<log::LevelFilter>()
.expect("Invalid log level");
let config = LoggerConfig {
level,
duplicate_to_stdout: if level == log::LevelFilter::Debug {
crate::config::Duplicate::All
} else {
crate::config::Duplicate::None
},
..LoggerConfig::default()
};
Self::init_with_config(config).expect("Logger init failed");
}
pub fn init_with_config(config: LoggerConfig) -> Result<(), Box<dyn std::error::Error>> {
let mut logger = FlexiLogger::try_with_env_or_str(config.level.as_str())?
.log_to_file(
FileSpec::default()
.directory(config.directory)
.basename(config.basename)
.suffix(config.suffix),
)
.rotate(
Criterion::AgeOrSize(Age::Day, config.rotate_size),
Naming::Timestamps,
Cleanup::KeepLogAndCompressedFiles(3, 90),
)
.write_mode(WriteMode::BufferAndFlush)
.duplicate_to_stdout(config.duplicate_to_stdout.into());
logger = logger.format_for_files(detailed_format);
logger = logger.format_for_stdout(console_format);
logger.start()?;
log::info!("Logger initialized, log level: {}", log::max_level());
Ok(())
}
}
fn detailed_format(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &log::Record,
) -> std::io::Result<()> {
let path = record
.file_static()
.unwrap_or(record.module_path().unwrap_or("<unnamed>"));
let path = extract_crate_path(path);
write!(
w,
"[{}] {:<5} [{:<55}] : {}",
now.now().format("%Y-%m-%d %H:%M:%S%.6f %:z"),
record.level(),
format!("{}:{}", path, record.line().unwrap_or(0)),
&record.args()
)
}
fn console_format(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &log::Record,
) -> std::io::Result<()> {
let level_str = match record.level() {
log::Level::Error => "\x1b[31mERROR\x1b[0m",
log::Level::Warn => "\x1b[33mWARN\x1b[0m",
log::Level::Info => "\x1b[32mINFO\x1b[0m",
log::Level::Debug => "\x1b[34mDEBUG\x1b[0m",
log::Level::Trace => "\x1b[36mTRACE\x1b[0m",
};
let path = record
.file_static()
.unwrap_or(record.module_path().unwrap_or("<unnamed>"));
let path = extract_crate_path(path);
write!(
w,
"[{}] {:<15} [{:<55}] {}",
now.now().format("%Y-%m-%d %H:%M:%S"),
level_str,
format!("{}:{}", path, record.line().unwrap_or(0)),
record.args()
)
}
fn extract_crate_path(full_path: &str) -> &str {
let src_tags = ["\\src\\", "/src/"];
for src_tag in &src_tags {
if let Some(src_pos) = full_path.rfind(src_tag) {
let before_src = &full_path[..src_pos];
let seps: Vec<_> = before_src
.char_indices()
.filter(|&(_, c)| c == '\\' || c == '/')
.map(|(i, _)| i)
.collect();
let start = if seps.len() >= 2 {
seps[seps.len() - 1] + 1
} else if seps.len() == 1 {
seps[0] + 1
} else {
0
};
return &full_path[start..];
}
}
full_path
}