extern crate parking_lot;
use self::parking_lot::Mutex;
use std::fs::{File, OpenOptions, create_dir_all, rename};
use std::path::{Path, PathBuf};
use std::fmt;
use std::io;
use std::io::Write;
use handlers::Handler;
pub enum RotatingFileHandlerError {
IoError(io::Error),
SizeError(u64),
CountError(usize),
}
impl From<io::Error> for RotatingFileHandlerError {
fn from(e: io::Error) -> Self {
RotatingFileHandlerError::IoError(e)
}
}
impl fmt::Debug for RotatingFileHandlerError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
RotatingFileHandlerError::IoError(ref err) => write!(f, "{}", err),
RotatingFileHandlerError::SizeError(ref size) => write!(f, "Invalid size {}", size),
RotatingFileHandlerError::CountError(ref count) => write!(f, "Invalid count {}", count),
}
}
}
struct Context {
path: PathBuf,
logs: Vec<PathBuf>,
size: u64,
current: u64,
file: File,
}
impl Context {
fn open(path: &Path) -> Result<File, RotatingFileHandlerError> {
match OpenOptions::new().append(true).create(true).open(path) {
Ok(file) => Ok(file),
Err(err) => Err(err.into()),
}
}
fn logs(path: &Path, count: usize) -> Vec<PathBuf> {
(0..count-1).rev().map(|r| PathBuf::from(format!("{}.{}", path.display(), r))).collect()
}
fn new(path: &Path, count: usize, size: u64) -> Result<Self, RotatingFileHandlerError> {
if let Some(dir) = path.parent() {
create_dir_all(dir)?;
}
let file = Self::open(path)?;
if size == 0 {
return Err(RotatingFileHandlerError::SizeError(size));
}
if count == 0 {
return Err(RotatingFileHandlerError::CountError(count));
}
Ok(Context {
path: path.into(),
logs: Self::logs(path, count),
size: size,
current: file.metadata()?.len(),
file: file,
})
}
fn ____emit(&mut self, msg: &[u8]) {
let _ = self.file.write_all(msg);
self.current += msg.len() as u64;
}
fn __emit(&mut self, msg: String) {
self.____emit(msg.as_bytes())
}
fn rotate(&mut self) {
let rlen = self.logs.len();
for i in 1..rlen {
let res = {
let old = &self.logs[i];
if old.exists() {
rename(old, &self.logs[i - 1])
} else {
Ok(())
}
};
if res.is_err() {
let msg = {
let old = self.logs[i].display();
let new = self.logs[i - 1].display();
format!("Failed to rename {} into {}: {}", old, new, res.unwrap_err())
};
self.__emit(msg);
}
}
let _ = self.file.flush();
if let Err(err) = rename(&self.path, &self.logs[rlen - 1]) {
let msg = {
let old = self.path.display();
let new = self.logs[rlen - 1].display();
format!("Failed to rename {} into {}: {}", old, new, err)
};
self.__emit(msg);
}
}
fn emit(&mut self, msg: &[u8]) {
self.____emit(msg);
if self.current >= self.size {
self.rotate();
match Self::open(&self.path) {
Ok(file) => {
self.file = file;
self.current = 0;
},
Err(err) => {
let msg = format!("Failed to open {}: {:?}", self.path.display(), err);
self.__emit(msg);
}
}
}
}
}
pub fn handler(path: &Path, count: usize, size: u64) -> Result<Handler, RotatingFileHandlerError> {
let ctx = Context::new(path, count, size)?;
let ctx = Mutex::new(ctx);
Ok(Box::new(move |record| {
let mut ctx = ctx.lock();
ctx.emit(record.formatted().as_bytes());
}))
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use self::tempdir::TempDir;
use super::*;
fn push(ctx: &mut super::Context, size: u64) {
ctx.emit("x".repeat(size as usize).as_bytes());
}
fn tlogs(logs: &[PathBuf], size: u64, filled: usize) {
for i in 0..filled {
let log = &logs[i];
assert!(log.exists());
assert_eq!(log.metadata().unwrap().len(), size);
}
for i in filled..logs.len() {
assert!(!logs[i].exists());
}
}
#[test]
fn test_rotating_file() {
let dir = TempDir::new("wp-rf").unwrap();
let path = dir.path().join("logs").join("test.log");
let count = 5;
let size = 20;
let mut ctx = super::Context::new(&path, count, size).unwrap();
let mut logs = super::Context::logs(&path, count + 1);
logs.reverse();
let elogs = &logs[..logs.len() - 1];
let flog = &logs[logs.len() - 1];
assert!(path.exists());
assert_eq!(path.metadata().unwrap().len(), 0);
push(&mut ctx, size / 2);
assert_eq!(path.metadata().unwrap().len(), size / 2);
tlogs(elogs, size, 0);
push(&mut ctx, size / 2);
assert_eq!(path.metadata().unwrap().len(), 0);
tlogs(elogs, size, 1);
for i in 2..count {
push(&mut ctx, size);
assert_eq!(path.metadata().unwrap().len(), 0);
tlogs(elogs, size, i);
}
assert!(!flog.exists());
}
}