log4rs_rolling_file/
lib.rs

1//! A rolling file appender for log4rs.
2//!
3//! Logging directly to a file can be a dangerous proposition for long running
4//! processes. You wouldn't want to start a server up and find out a couple
5//! weeks later that the disk is filled with hundreds of gigabytes of logs! A
6//! rolling file appender alleviates these issues by limiting the amount of log
7//! data that's preserved.
8//!
9//! Like a normal file appender, a rolling file appender is configured with the
10//! location of its log file and the encoder which formats log events written
11//! to it. In addition, it holds a "policy" object which controls when a log
12//! file is rolled over and how the old files are archived.
13//!
14//! For example, you may configure an appender to roll the log over once it
15//! reaches 50 megabytes, and to preserve the last 10 log files.
16#![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
54/// Registers this crate's deserializers.
55///
56/// The following mappings will be added:
57///
58/// * Appenders
59///   * "rolling_file" -> `RollingFileAppenderDeserializer`
60/// * Policies
61///   *  "compound" -> `CompoundPolicyDeserializer`
62/// * Triggers
63///   * "size" -> `SizeTriggerDeserializer`
64/// * Rollers
65///   * "delete" -> `DeleteRollerDeserializer`
66///   * "fixed_window" -> `FixedWindowRollerDeserializer`
67pub 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
97/// Information about the active log file.
98pub struct LogFile<'a> {
99    writer: &'a mut Option<LogWriter>,
100    path: &'a Path,
101    len: u64,
102}
103
104impl<'a> LogFile<'a> {
105    /// Returns the path to the log file.
106    pub fn path(&self) -> &Path {
107        self.path
108    }
109
110    /// Returns an estimate of the log file's current size.
111    ///
112    /// This is calculated by taking the size of the log file when it is opened
113    /// and adding the number of bytes written. It may be inaccurate if any
114    /// writes have failed or if another process has modified the file
115    /// concurrently.
116    pub fn len(&self) -> u64 {
117        self.len
118    }
119
120    /// Triggers the log file to roll over.
121    ///
122    /// A policy must call this method when it wishes to roll the log. The
123    /// appender's handle to the file will be closed, which is necessary to
124    /// move or delete the file on Windows.
125    ///
126    /// If this method is called, the log file must no longer be present when
127    /// the policy returns.
128    pub fn roll(&mut self) {
129        *self.writer = None;
130    }
131}
132
133/// An appender which archives log files in a configurable strategy.
134pub 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            // :( unwrap
181            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    /// Creates a new `RollingFileAppenderBuilder`.
199    pub fn builder() -> RollingFileAppenderBuilder {
200        RollingFileAppenderBuilder {
201            append: true,
202            encoder: None,
203        }
204    }
205}
206
207/// A builder for the `RollingFileAppender`.
208pub struct RollingFileAppenderBuilder {
209    append: bool,
210    encoder: Option<Box<Encode>>,
211}
212
213impl RollingFileAppenderBuilder {
214    /// Determines if the appender will append to or truncate the log file.
215    ///
216    /// Defaults to `true`.
217    pub fn append(mut self, append: bool) -> RollingFileAppenderBuilder {
218        self.append = append;
219        self
220    }
221
222    /// Sets the encoder used by the appender.
223    ///
224    /// Defaults to a `PatternEncoder` with the default pattern.
225    pub fn encoder(mut self, encoder: Box<Encode>) -> RollingFileAppenderBuilder {
226        self.encoder = Some(encoder);
227        self
228    }
229
230    /// Constructs a `RollingFileAppender`.
231    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
244/// A deserializer for the `RollingFileAppender`.
245///
246/// # Configuration
247///
248/// ```yaml
249/// kind: rolling_file
250///
251/// # The path of the log file. Required.
252/// path: log/foo.log
253///
254/// # Specifies if the appender should append to or truncate the log file if it
255/// # already exists. Defaults to `true`.
256/// append: true
257///
258/// # The encoder to use to format output. Defaults to `kind: pattern`.
259/// encoder:
260///   kind: pattern
261///
262/// # The policy which handles rotation of the log file. Required.
263/// policy:
264///   # Identifies which policy is to be used. If no kind is specified, it will
265///   # default to "compound".
266///   kind: compound
267///
268///   # The remainder of the configuration is passed along to the policy's
269///   # deserializer, and will vary based on the kind of policy.
270///   trigger:
271///     kind: size
272///     limit: 10 mb
273///
274///   roller:
275///     kind: delete
276/// ```
277pub 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}