use std::{
fs::{self, File, OpenOptions},
io::Write,
path::{Path, PathBuf},
};
use crate::RuntimeResult;
const MAX_ROTATED_FILES: u32 = 3;
pub struct RotatingLog {
path: PathBuf,
file: File,
max_bytes: u64,
written: u64,
}
impl RotatingLog {
pub fn new(log_dir: &Path, prefix: &str, max_bytes: u64) -> RuntimeResult<Self> {
fs::create_dir_all(log_dir)?;
let path = log_dir.join(format!("{prefix}.log"));
let written = path.metadata().map(|m| m.len()).unwrap_or(0);
let file = OpenOptions::new().create(true).append(true).open(&path)?;
Ok(Self {
path,
file,
max_bytes,
written,
})
}
pub fn write(&mut self, data: &[u8]) -> RuntimeResult<()> {
if self.written + data.len() as u64 > self.max_bytes {
self.rotate()?;
}
self.file.write_all(data)?;
self.written += data.len() as u64;
Ok(())
}
pub fn flush(&mut self) -> RuntimeResult<()> {
self.file.flush()?;
Ok(())
}
}
impl RotatingLog {
fn rotate(&mut self) -> RuntimeResult<()> {
self.file.flush()?;
for i in (1..=MAX_ROTATED_FILES).rev() {
let from = format!("{}.{i}", self.path.display());
let to = format!("{}.{}", self.path.display(), i + 1);
if Path::new(&from).exists() {
fs::rename(&from, &to)?;
}
}
let rotated = format!("{}.1", self.path.display());
fs::rename(&self.path, &rotated)?;
self.file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
self.written = 0;
Ok(())
}
}