use super::logging::try_log;
use crate::{Config, SharedLogger};
use log::{set_boxed_logger, set_max_level, LevelFilter, Log, Metadata, Record, SetLoggerError};
use std::fs::remove_file;
use std::fs::rename;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::ErrorKind;
use std::io::Write;
use std::sync::Mutex;
pub struct FileLogger {
level: LevelFilter,
config: Config,
writable: Mutex<File>,
max_size: Option<u64>, file_path: String,
}
impl FileLogger {
pub fn init(
log_level: LevelFilter,
config: Config,
file_path: &str,
max_size: Option<u64>,
) -> Result<(), SetLoggerError> {
set_max_level(log_level);
set_boxed_logger(Self::new(log_level, config, file_path, max_size))
}
fn rotate(&self) {
if let Some(max_size) = self.max_size {
let writable = self.writable.lock().unwrap();
if let Ok(metadata) = writable.metadata() {
if metadata.len() > max_size {
drop(writable);
let backup_path = format!("{}.bak", self.file_path);
if let Err(err) = rename(&self.file_path, &backup_path) {
eprintln!("Error moving log file to backup: {}", err);
}
let new_file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.file_path)
.unwrap();
*self.writable.lock().unwrap() = new_file;
}
}
}
}
#[must_use]
pub fn new(
log_level: LevelFilter,
config: Config,
file_path: &str,
max_size: Option<u64>,
) -> Box<Self> {
let backup_path = format!("{}.bak", file_path);
if let Err(err) = remove_file(&backup_path) {
if err.kind() != ErrorKind::NotFound {
eprintln!(
"Failed to remove existing backup file {}: {}",
backup_path, err
);
}
}
let file = OpenOptions::new()
.create(true)
.append(true)
.open(file_path)
.unwrap();
Box::new(Self {
level: log_level,
config,
writable: Mutex::new(file),
max_size,
file_path: file_path.to_string(),
})
}
}
impl Log for FileLogger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &Record<'_>) {
if self.enabled(record.metadata()) {
self.rotate();
let mut write_lock = self.writable.lock().unwrap();
let _ = try_log(&self.config, record, &mut *write_lock);
}
}
fn flush(&self) {
let _ = self.writable.lock().unwrap().flush();
}
}
impl SharedLogger for FileLogger {
fn level(&self) -> LevelFilter {
self.level
}
fn config(&self) -> Option<&Config> {
Some(&self.config)
}
fn as_log(self: Box<Self>) -> Box<dyn Log> {
Box::new(*self)
}
}