#![doc(html_root_url="https://sfackler.github.io/log4rs-rolling-file/doc/v0.2.0")]
#![warn(missing_docs)]
extern crate antidote;
extern crate log;
extern crate log4rs;
extern crate serde;
extern crate serde_value;
#[cfg(feature = "gzip")]
extern crate flate2;
#[cfg(test)]
extern crate tempdir;
use antidote::Mutex;
use log4rs::append::Append;
use log4rs::encode::{self, Encode};
use log4rs::encode::pattern::PatternEncoder;
use log4rs::file::{Deserialize, Deserializers};
use log::LogRecord;
use std::error::Error;
use std::fmt;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write, BufWriter};
use std::path::{Path, PathBuf};
use serde_value::Value;
use config::Config;
use policy::Policy;
use policy::compound::CompoundPolicyDeserializer;
use policy::compound::trigger::size::SizeTriggerDeserializer;
use policy::compound::roll::delete::DeleteRollerDeserializer;
use policy::compound::roll::fixed_window::FixedWindowRollerDeserializer;
pub mod policy;
#[cfg_attr(rustfmt, rustfmt_skip)]
mod config;
pub fn register(d: &mut Deserializers) {
d.insert("rolling_file".to_owned(),
Box::new(RollingFileAppenderDeserializer));
d.insert("compound".to_owned(), Box::new(CompoundPolicyDeserializer));
d.insert("size".to_owned(), Box::new(SizeTriggerDeserializer));
d.insert("delete".to_owned(), Box::new(DeleteRollerDeserializer));
d.insert("fixed_window".to_owned(),
Box::new(FixedWindowRollerDeserializer));
}
struct LogWriter {
file: BufWriter<File>,
len: u64,
}
impl io::Write for LogWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf).map(|n| {
self.len += n as u64;
n
})
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl encode::Write for LogWriter {}
pub struct LogFile<'a> {
writer: &'a mut Option<LogWriter>,
path: &'a Path,
len: u64,
}
impl<'a> LogFile<'a> {
pub fn path(&self) -> &Path {
self.path
}
pub fn len(&self) -> u64 {
self.len
}
pub fn roll(&mut self) {
*self.writer = None;
}
}
pub struct RollingFileAppender {
writer: Mutex<Option<LogWriter>>,
path: PathBuf,
append: bool,
encoder: Box<Encode>,
policy: Box<Policy>,
}
impl fmt::Debug for RollingFileAppender {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("RollingFileAppender")
.field("path", &self.path)
.field("append", &self.append)
.field("encoder", &self.encoder)
.field("policy", &self.policy)
.finish()
}
}
impl Append for RollingFileAppender {
fn append(&self, record: &LogRecord) -> Result<(), Box<Error>> {
let mut writer = self.writer.lock();
if writer.is_none() {
if let Some(parent) = self.path.parent() {
try!(fs::create_dir_all(parent));
}
let file = try!(OpenOptions::new()
.write(true)
.append(self.append)
.truncate(!self.append)
.create(true)
.open(&self.path));
let len = if self.append {
try!(self.path.metadata()).len()
} else {
0
};
*writer = Some(LogWriter {
file: BufWriter::with_capacity(1024, file),
len: len,
});
}
let len = {
let writer = writer.as_mut().unwrap();
try!(self.encoder.encode(writer, record));
try!(writer.flush());
writer.len
};
let mut file = LogFile {
writer: &mut writer,
path: &self.path,
len: len,
};
self.policy.process(&mut file)
}
}
impl RollingFileAppender {
pub fn builder() -> RollingFileAppenderBuilder {
RollingFileAppenderBuilder {
append: true,
encoder: None,
}
}
}
pub struct RollingFileAppenderBuilder {
append: bool,
encoder: Option<Box<Encode>>,
}
impl RollingFileAppenderBuilder {
pub fn append(mut self, append: bool) -> RollingFileAppenderBuilder {
self.append = append;
self
}
pub fn encoder(mut self, encoder: Box<Encode>) -> RollingFileAppenderBuilder {
self.encoder = Some(encoder);
self
}
pub fn build<P>(self, path: P, policy: Box<Policy>) -> RollingFileAppender
where P: AsRef<Path>
{
RollingFileAppender {
writer: Mutex::new(None),
path: path.as_ref().to_owned(),
append: self.append,
encoder: self.encoder.unwrap_or_else(|| Box::new(PatternEncoder::default())),
policy: policy,
}
}
}
pub struct RollingFileAppenderDeserializer;
impl Deserialize for RollingFileAppenderDeserializer {
type Trait = Append;
fn deserialize(&self,
config: Value,
deserializers: &Deserializers)
-> Result<Box<Append>, Box<Error>> {
let config: Config = try!(config.deserialize_into());
let mut builder = RollingFileAppender::builder();
if let Some(append) = config.append {
builder = builder.append(append);
}
if let Some(encoder) = config.encoder {
let encoder = try!(deserializers.deserialize("encoder", &encoder.kind, encoder.config));
builder = builder.encoder(encoder);
}
let policy =
try!(deserializers.deserialize("policy", &config.policy.kind, config.policy.config));
Ok(Box::new(builder.build(config.path, policy)))
}
}
#[cfg(test)]
mod test {
use log4rs::file::{Config, Deserializers, Format};
use super::*;
#[test]
fn deserialize() {
let config = "
appenders:
foo:
kind: rolling_file
path: foo.log
policy:
trigger:
kind: size
limit: 1024
roller:
kind: delete
bar:
kind: rolling_file
path: foo.log
policy:
kind: compound
trigger:
kind: size
limit: 5 mb
roller:
kind: fixed_window
pattern: 'foo.log.{}'
base: 1
count: 5
";
let mut deserializers = Deserializers::default();
register(&mut deserializers);
let config = Config::parse(config, Format::Yaml, &deserializers).unwrap();
println!("{:?}", config.errors());
assert!(config.errors().is_empty());
}
}