log4rs_rolling_file/
lib.rs1#![doc(html_root_url="https://sfackler.github.io/log4rs-rolling-file/doc/v0.2.0")]
17#![warn(missing_docs)]
18extern crate antidote;
19extern crate log;
20extern crate log4rs;
21extern crate serde;
22extern crate serde_value;
23
24#[cfg(feature = "gzip")]
25extern crate flate2;
26
27#[cfg(test)]
28extern crate tempdir;
29
30use antidote::Mutex;
31use log4rs::append::Append;
32use log4rs::encode::{self, Encode};
33use log4rs::encode::pattern::PatternEncoder;
34use log4rs::file::{Deserialize, Deserializers};
35use log::LogRecord;
36use std::error::Error;
37use std::fmt;
38use std::fs::{self, File, OpenOptions};
39use std::io::{self, Write, BufWriter};
40use std::path::{Path, PathBuf};
41use serde_value::Value;
42
43use config::Config;
44use policy::Policy;
45use policy::compound::CompoundPolicyDeserializer;
46use policy::compound::trigger::size::SizeTriggerDeserializer;
47use policy::compound::roll::delete::DeleteRollerDeserializer;
48use policy::compound::roll::fixed_window::FixedWindowRollerDeserializer;
49
50pub mod policy;
51#[cfg_attr(rustfmt, rustfmt_skip)]
52mod config;
53
54pub fn register(d: &mut Deserializers) {
68 d.insert("rolling_file".to_owned(),
69 Box::new(RollingFileAppenderDeserializer));
70 d.insert("compound".to_owned(), Box::new(CompoundPolicyDeserializer));
71 d.insert("size".to_owned(), Box::new(SizeTriggerDeserializer));
72 d.insert("delete".to_owned(), Box::new(DeleteRollerDeserializer));
73 d.insert("fixed_window".to_owned(),
74 Box::new(FixedWindowRollerDeserializer));
75}
76
77struct LogWriter {
78 file: BufWriter<File>,
79 len: u64,
80}
81
82impl io::Write for LogWriter {
83 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
84 self.file.write(buf).map(|n| {
85 self.len += n as u64;
86 n
87 })
88 }
89
90 fn flush(&mut self) -> io::Result<()> {
91 self.file.flush()
92 }
93}
94
95impl encode::Write for LogWriter {}
96
97pub struct LogFile<'a> {
99 writer: &'a mut Option<LogWriter>,
100 path: &'a Path,
101 len: u64,
102}
103
104impl<'a> LogFile<'a> {
105 pub fn path(&self) -> &Path {
107 self.path
108 }
109
110 pub fn len(&self) -> u64 {
117 self.len
118 }
119
120 pub fn roll(&mut self) {
129 *self.writer = None;
130 }
131}
132
133pub struct RollingFileAppender {
135 writer: Mutex<Option<LogWriter>>,
136 path: PathBuf,
137 append: bool,
138 encoder: Box<Encode>,
139 policy: Box<Policy>,
140}
141
142impl fmt::Debug for RollingFileAppender {
143 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
144 fmt.debug_struct("RollingFileAppender")
145 .field("path", &self.path)
146 .field("append", &self.append)
147 .field("encoder", &self.encoder)
148 .field("policy", &self.policy)
149 .finish()
150 }
151}
152
153impl Append for RollingFileAppender {
154 fn append(&self, record: &LogRecord) -> Result<(), Box<Error>> {
155 let mut writer = self.writer.lock();
156
157 if writer.is_none() {
158 if let Some(parent) = self.path.parent() {
159 try!(fs::create_dir_all(parent));
160 }
161
162 let file = try!(OpenOptions::new()
163 .write(true)
164 .append(self.append)
165 .truncate(!self.append)
166 .create(true)
167 .open(&self.path));
168 let len = if self.append {
169 try!(self.path.metadata()).len()
170 } else {
171 0
172 };
173 *writer = Some(LogWriter {
174 file: BufWriter::with_capacity(1024, file),
175 len: len,
176 });
177 }
178
179 let len = {
180 let writer = writer.as_mut().unwrap();
182 try!(self.encoder.encode(writer, record));
183 try!(writer.flush());
184 writer.len
185 };
186
187 let mut file = LogFile {
188 writer: &mut writer,
189 path: &self.path,
190 len: len,
191 };
192
193 self.policy.process(&mut file)
194 }
195}
196
197impl RollingFileAppender {
198 pub fn builder() -> RollingFileAppenderBuilder {
200 RollingFileAppenderBuilder {
201 append: true,
202 encoder: None,
203 }
204 }
205}
206
207pub struct RollingFileAppenderBuilder {
209 append: bool,
210 encoder: Option<Box<Encode>>,
211}
212
213impl RollingFileAppenderBuilder {
214 pub fn append(mut self, append: bool) -> RollingFileAppenderBuilder {
218 self.append = append;
219 self
220 }
221
222 pub fn encoder(mut self, encoder: Box<Encode>) -> RollingFileAppenderBuilder {
226 self.encoder = Some(encoder);
227 self
228 }
229
230 pub fn build<P>(self, path: P, policy: Box<Policy>) -> RollingFileAppender
232 where P: AsRef<Path>
233 {
234 RollingFileAppender {
235 writer: Mutex::new(None),
236 path: path.as_ref().to_owned(),
237 append: self.append,
238 encoder: self.encoder.unwrap_or_else(|| Box::new(PatternEncoder::default())),
239 policy: policy,
240 }
241 }
242}
243
244pub struct RollingFileAppenderDeserializer;
278
279impl Deserialize for RollingFileAppenderDeserializer {
280 type Trait = Append;
281
282 fn deserialize(&self,
283 config: Value,
284 deserializers: &Deserializers)
285 -> Result<Box<Append>, Box<Error>> {
286 let config: Config = try!(config.deserialize_into());
287
288 let mut builder = RollingFileAppender::builder();
289 if let Some(append) = config.append {
290 builder = builder.append(append);
291 }
292 if let Some(encoder) = config.encoder {
293 let encoder = try!(deserializers.deserialize("encoder", &encoder.kind, encoder.config));
294 builder = builder.encoder(encoder);
295 }
296
297 let policy =
298 try!(deserializers.deserialize("policy", &config.policy.kind, config.policy.config));
299 Ok(Box::new(builder.build(config.path, policy)))
300 }
301}
302
303#[cfg(test)]
304mod test {
305 use log4rs::file::{Config, Deserializers, Format};
306
307 use super::*;
308
309 #[test]
310 fn deserialize() {
311 let config = "
312appenders:
313 foo:
314 kind: rolling_file
315 path: foo.log
316 policy:
317 trigger:
318 kind: size
319 limit: 1024
320 roller:
321 kind: delete
322 bar:
323 kind: rolling_file
324 path: foo.log
325 policy:
326 kind: compound
327 trigger:
328 kind: size
329 limit: 5 mb
330 roller:
331 kind: fixed_window
332 pattern: 'foo.log.{}'
333 base: 1
334 count: 5
335";
336
337 let mut deserializers = Deserializers::default();
338 register(&mut deserializers);
339
340 let config = Config::parse(config, Format::Yaml, &deserializers).unwrap();
341 println!("{:?}", config.errors());
342 assert!(config.errors().is_empty());
343 }
344}