use log4rs::file::{Deserialize, Deserializers};
use serde_value::Value;
use std::error::Error;
use std::fs;
use std::io;
use std::path::Path;
use policy::compound::roll::Roll;
use policy::compound::roll::fixed_window::config::Config;
#[cfg_attr(rustfmt, rustfmt_skip)]
mod config;
#[derive(Debug)]
enum Compression {
None,
#[cfg(feature = "gzip")]
Gzip,
}
impl Compression {
fn compress(&self, src: &Path, dst: &str) -> io::Result<()> {
match *self {
Compression::None => move_file(src, dst),
#[cfg(feature = "gzip")]
Compression::Gzip => {
use flate2;
use flate2::write::GzEncoder;
use std::fs::File;
let mut i = try!(File::open(src));
let o = try!(File::create(dst));
let mut o = GzEncoder::new(o, flate2::Compression::Default);
try!(io::copy(&mut i, &mut o));
drop(try!(o.finish()));
drop(i);
fs::remove_file(src)
}
}
}
}
#[derive(Debug)]
pub struct FixedWindowRoller {
pattern: String,
compression: Compression,
base: u32,
count: u32,
}
impl FixedWindowRoller {
pub fn builder() -> FixedWindowRollerBuilder {
FixedWindowRollerBuilder { base: 0 }
}
}
impl Roll for FixedWindowRoller {
fn roll(&self, file: &Path) -> Result<(), Box<Error>> {
if self.count == 0 {
return fs::remove_file(file).map_err(Into::into);
}
let dst_0 = self.pattern.replace("{}", "0");
if let Some(parent) = Path::new(&dst_0).parent() {
try!(fs::create_dir_all(parent));
}
let parent_varies = match (Path::new(&dst_0).parent(), Path::new(&self.pattern).parent()) {
(Some(a), Some(b)) => a != b,
_ => false, };
for i in (self.base..self.base + self.count - 1).rev() {
let src = self.pattern.replace("{}", &i.to_string());
let dst = self.pattern.replace("{}", &(i + 1).to_string());
if parent_varies {
if let Some(parent) = Path::new(&dst).parent() {
try!(fs::create_dir_all(parent));
}
}
try!(move_file(&src, &dst));
}
self.compression.compress(file, &self.pattern.replace("{}", "0")).map_err(Into::into)
}
}
fn move_file<P>(src: P, dst: &str) -> io::Result<()>
where P: AsRef<Path>
{
match fs::rename(src.as_ref(), dst) {
Ok(()) => return Ok(()),
Err(ref e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(_) => {}
}
fs::copy(src.as_ref(), dst).and_then(|_| fs::remove_file(src.as_ref()))
}
pub struct FixedWindowRollerBuilder {
base: u32,
}
impl FixedWindowRollerBuilder {
pub fn base(mut self, base: u32) -> FixedWindowRollerBuilder {
self.base = base;
self
}
pub fn build(self, pattern: &str, count: u32) -> Result<FixedWindowRoller, Box<Error>> {
if !pattern.contains("{}") {
return Err("pattern does not contain `{}`".into());
}
let compression = match Path::new(pattern).extension() {
#[cfg(feature = "gzip")]
Some(e) if e == "gz" => Compression::Gzip,
#[cfg(not(feature = "gzip"))]
Some(e) if e == "gz" => {
return Err("gzip compression requires the `gzip` feature".into());
}
_ => Compression::None,
};
Ok(FixedWindowRoller {
pattern: pattern.to_owned(),
compression: compression,
base: self.base,
count: count,
})
}
}
pub struct FixedWindowRollerDeserializer;
impl Deserialize for FixedWindowRollerDeserializer {
type Trait = Roll;
fn deserialize(&self, config: Value, _: &Deserializers) -> Result<Box<Roll>, Box<Error>> {
let config: Config = try!(config.deserialize_into());
let mut builder = FixedWindowRoller::builder();
if let Some(base) = config.base {
builder = builder.base(base);
}
Ok(Box::new(try!(builder.build(&config.pattern, config.count))))
}
}
#[cfg(test)]
mod test {
use tempdir::TempDir;
use std::fs::File;
use std::io::{Read, Write};
use std::process::Command;
use policy::compound::roll::Roll;
use super::*;
#[test]
fn rotation() {
let dir = TempDir::new("rotation").unwrap();
let base = dir.path().to_str().unwrap();
let roller = FixedWindowRoller::builder()
.build(&format!("{}/foo.log.{{}}", base), 2)
.unwrap();
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(b"file1").unwrap();
roller.roll(&file).unwrap();
assert!(!file.exists());
let mut contents = vec![];
File::open(dir.path().join("foo.log.0")).unwrap().read_to_end(&mut contents).unwrap();
assert_eq!(contents, b"file1");
File::create(&file).unwrap().write_all(b"file2").unwrap();
roller.roll(&file).unwrap();
assert!(!file.exists());
contents.clear();
File::open(dir.path().join("foo.log.1")).unwrap().read_to_end(&mut contents).unwrap();
assert_eq!(contents, b"file1");
contents.clear();
File::open(dir.path().join("foo.log.0")).unwrap().read_to_end(&mut contents).unwrap();
assert_eq!(contents, b"file2");
File::create(&file).unwrap().write_all(b"file3").unwrap();
roller.roll(&file).unwrap();
assert!(!file.exists());
contents.clear();
assert!(!dir.path().join("foo.log.2").exists());
File::open(dir.path().join("foo.log.1")).unwrap().read_to_end(&mut contents).unwrap();
assert_eq!(contents, b"file2");
contents.clear();
File::open(dir.path().join("foo.log.0")).unwrap().read_to_end(&mut contents).unwrap();
assert_eq!(contents, b"file3");
}
#[test]
fn create_archive_unvaried() {
let dir = TempDir::new("create_archive_unvaried").unwrap();
let base = dir.path().join("log").join("archive");
let pattern = base.join("foo.{}.log");
let roller = FixedWindowRoller::builder()
.build(pattern.to_str().unwrap(), 2)
.unwrap();
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(b"file").unwrap();
roller.roll(&file).unwrap();
assert!(base.join("foo.0.log").exists());
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(b"file2").unwrap();
roller.roll(&file).unwrap();
assert!(base.join("foo.0.log").exists());
assert!(base.join("foo.1.log").exists());
}
#[test]
fn create_archive_varied() {
let dir = TempDir::new("create_archive_unvaried").unwrap();
let base = dir.path().join("log").join("archive");
let pattern = base.join("{}").join("foo.log");
let roller = FixedWindowRoller::builder()
.build(pattern.to_str().unwrap(), 2)
.unwrap();
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(b"file").unwrap();
roller.roll(&file).unwrap();
assert!(base.join("0").join("foo.log").exists());
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(b"file2").unwrap();
roller.roll(&file).unwrap();
assert!(base.join("0").join("foo.log").exists());
assert!(base.join("1").join("foo.log").exists());
}
#[test]
#[cfg_attr(feature = "gzip", ignore)]
fn unsupported_gzip() {
let dir = TempDir::new("unsupported_gzip").unwrap();
let pattern = dir.path().join("{}.gz");
assert!(FixedWindowRoller::builder().build(pattern.to_str().unwrap(), 2).is_err());
}
#[test]
#[cfg_attr(not(feature = "gzip"), ignore)]
fn supported_gzip() {
let dir = TempDir::new("supported_gzip").unwrap();
let pattern = dir.path().join("{}.gz");
let roller = FixedWindowRoller::builder().build(pattern.to_str().unwrap(), 2).unwrap();
let contents = (0..10000).map(|i| i as u8).collect::<Vec<_>>();
let file = dir.path().join("foo.log");
File::create(&file).unwrap().write_all(&contents).unwrap();
roller.roll(&file).unwrap();
assert!(Command::new("gunzip")
.arg(dir.path().join("0.gz"))
.status()
.unwrap()
.success());
let mut file = File::open(dir.path().join("0")).unwrap();
let mut actual = vec![];
file.read_to_end(&mut actual).unwrap();
assert_eq!(contents, actual);
}
}