tracing_rolling_file/
base.rs

1//! Implements a rolling condition based on a certain frequency
2//! and/or a size limit. The default condition is to rotate daily.
3//!
4//! # Examples
5//!
6//! ```rust
7//! use tracing_rolling_file::*;
8//! let c = RollingConditionBase::new().daily();
9//! let c = RollingConditionBase::new().hourly().max_size(1024 * 1024);
10//! ```
11
12use crate::*;
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub struct RollingConditionBase {
16    last_write_opt: Option<DateTime<Local>>,
17    frequency_opt: Option<RollingFrequency>,
18    max_size_opt: Option<u64>,
19}
20
21impl RollingConditionBase {
22    /// Constructs a new struct that does not yet have any condition set.
23    pub fn new() -> RollingConditionBase {
24        RollingConditionBase {
25            last_write_opt: None,
26            frequency_opt: None,
27            max_size_opt: None,
28        }
29    }
30
31    /// Sets a condition to rollover on the given frequency
32    pub fn frequency(mut self, x: RollingFrequency) -> RollingConditionBase {
33        self.frequency_opt = Some(x);
34        self
35    }
36
37    /// Sets a condition to rollover when the date changes
38    pub fn daily(mut self) -> RollingConditionBase {
39        self.frequency_opt = Some(RollingFrequency::EveryDay);
40        self
41    }
42
43    /// Sets a condition to rollover when the date or hour changes
44    pub fn hourly(mut self) -> RollingConditionBase {
45        self.frequency_opt = Some(RollingFrequency::EveryHour);
46        self
47    }
48
49    /// Sets a condition to rollover when the date or minute changes
50    pub fn minutely(mut self) -> RollingConditionBase {
51        self.frequency_opt = Some(RollingFrequency::EveryMinute);
52        self
53    }
54
55    /// Sets a condition to rollover when a certain size is reached
56    pub fn max_size(mut self, x: u64) -> RollingConditionBase {
57        self.max_size_opt = Some(x);
58        self
59    }
60}
61
62impl Default for RollingConditionBase {
63    fn default() -> Self {
64        RollingConditionBase::new().frequency(RollingFrequency::EveryDay)
65    }
66}
67
68impl RollingCondition for RollingConditionBase {
69    fn should_rollover(&mut self, now: &DateTime<Local>, current_filesize: u64) -> bool {
70        let mut rollover = false;
71        if let Some(frequency) = self.frequency_opt.as_ref() {
72            if let Some(last_write) = self.last_write_opt.as_ref() {
73                if frequency.equivalent_datetime(now) != frequency.equivalent_datetime(last_write) {
74                    rollover = true;
75                }
76            }
77        }
78        if let Some(max_size) = self.max_size_opt.as_ref() {
79            if current_filesize >= *max_size {
80                rollover = true;
81            }
82        }
83        self.last_write_opt = Some(*now);
84        rollover
85    }
86}
87
88/// A rolling file appender with a rolling condition based on date/time or size.
89pub type RollingFileAppenderBase = RollingFileAppender<RollingConditionBase>;
90
91// LCOV_EXCL_START
92#[cfg(test)]
93mod test {
94    use super::*;
95
96    struct Context {
97        _tempdir: tempfile::TempDir,
98        rolling: RollingFileAppenderBase,
99    }
100
101    impl Context {
102        fn verify_contains(&mut self, needle: &str, n: usize) {
103            self.rolling.flush().unwrap();
104            let p = self.rolling.filename_for(n);
105            let haystack = fs::read_to_string(&p).unwrap();
106            if !haystack.contains(needle) {
107                panic!("file {:?} did not contain expected contents {}", p, needle);
108            }
109        }
110    }
111
112    fn build_context(condition: RollingConditionBase, max_files: usize) -> Context {
113        let tempdir = tempfile::tempdir().unwrap();
114        let filename = tempdir.path().join("test.log");
115        let rolling = RollingFileAppenderBase::new(filename, condition, max_files).unwrap();
116        Context {
117            _tempdir: tempdir,
118            rolling,
119        }
120    }
121
122    #[test]
123    fn frequency_every_day() {
124        let mut c = build_context(RollingConditionBase::new().daily(), 9);
125        c.rolling
126            .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
127            .unwrap();
128        c.rolling
129            .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
130            .unwrap();
131        c.rolling
132            .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 31, 1, 4, 0).unwrap())
133            .unwrap();
134        c.rolling
135            .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 5, 31, 1, 4, 0).unwrap())
136            .unwrap();
137        c.rolling
138            .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2022, 5, 31, 1, 4, 0).unwrap())
139            .unwrap();
140        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
141        c.verify_contains("Line 1", 3);
142        c.verify_contains("Line 2", 3);
143        c.verify_contains("Line 3", 2);
144        c.verify_contains("Line 4", 1);
145        c.verify_contains("Line 5", 0);
146    }
147
148    #[test]
149    fn frequency_every_day_limited_files() {
150        let mut c = build_context(RollingConditionBase::new().daily(), 2);
151        c.rolling
152            .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
153            .unwrap();
154        c.rolling
155            .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
156            .unwrap();
157        c.rolling
158            .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 31, 1, 4, 0).unwrap())
159            .unwrap();
160        c.rolling
161            .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 5, 31, 1, 4, 0).unwrap())
162            .unwrap();
163        c.rolling
164            .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2022, 5, 31, 1, 4, 0).unwrap())
165            .unwrap();
166        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
167        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
168        c.verify_contains("Line 3", 2);
169        c.verify_contains("Line 4", 1);
170        c.verify_contains("Line 5", 0);
171    }
172
173    #[test]
174    fn frequency_every_hour() {
175        let mut c = build_context(RollingConditionBase::new().hourly(), 9);
176        c.rolling
177            .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
178            .unwrap();
179        c.rolling
180            .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 2).unwrap())
181            .unwrap();
182        c.rolling
183            .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 30, 2, 1, 0).unwrap())
184            .unwrap();
185        c.rolling
186            .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 3, 31, 2, 1, 0).unwrap())
187            .unwrap();
188        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
189        c.verify_contains("Line 1", 2);
190        c.verify_contains("Line 2", 2);
191        c.verify_contains("Line 3", 1);
192        c.verify_contains("Line 4", 0);
193    }
194
195    #[test]
196    fn frequency_every_minute() {
197        let mut c = build_context(RollingConditionBase::new().frequency(RollingFrequency::EveryMinute), 9);
198        c.rolling
199            .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
200            .unwrap();
201        c.rolling
202            .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
203            .unwrap();
204        c.rolling
205            .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 4).unwrap())
206            .unwrap();
207        c.rolling
208            .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
209            .unwrap();
210        c.rolling
211            .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 0).unwrap())
212            .unwrap();
213        c.rolling
214            .write_with_datetime(b"Line 6\n", &Local.with_ymd_and_hms(2022, 3, 30, 2, 3, 0).unwrap())
215            .unwrap();
216        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
217        c.verify_contains("Line 1", 3);
218        c.verify_contains("Line 2", 3);
219        c.verify_contains("Line 3", 3);
220        c.verify_contains("Line 4", 2);
221        c.verify_contains("Line 5", 1);
222        c.verify_contains("Line 6", 0);
223    }
224
225    #[test]
226    fn max_size() {
227        let mut c = build_context(RollingConditionBase::new().max_size(10), 9);
228        c.rolling
229            .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
230            .unwrap();
231        c.rolling
232            .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 3).unwrap())
233            .unwrap();
234        c.rolling
235            .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
236            .unwrap();
237        c.rolling
238            .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
239            .unwrap();
240        c.rolling
241            .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2022, 3, 31, 1, 2, 3).unwrap())
242            .unwrap();
243        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
244        c.verify_contains("1234567890", 2);
245        c.verify_contains("abcdefghijkl", 1);
246        c.verify_contains("ZZZ", 0);
247    }
248
249    #[test]
250    fn max_size_existing() {
251        let mut c = build_context(RollingConditionBase::new().max_size(10), 9);
252        c.rolling
253            .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
254            .unwrap();
255        // close the file and make sure that it can re-open it, and that it
256        // resets the file size properly.
257        c.rolling.writer_opt.take();
258        c.rolling.current_filesize = 0;
259        c.rolling
260            .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 3).unwrap())
261            .unwrap();
262        c.rolling
263            .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
264            .unwrap();
265        c.rolling
266            .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
267            .unwrap();
268        c.rolling
269            .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2022, 3, 31, 1, 2, 3).unwrap())
270            .unwrap();
271        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
272        c.verify_contains("1234567890", 2);
273        c.verify_contains("abcdefghijkl", 1);
274        c.verify_contains("ZZZ", 0);
275    }
276
277    #[test]
278    fn daily_and_max_size() {
279        let mut c = build_context(RollingConditionBase::new().daily().max_size(10), 9);
280        c.rolling
281            .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
282            .unwrap();
283        c.rolling
284            .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
285            .unwrap();
286        c.rolling
287            .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
288            .unwrap();
289        c.rolling
290            .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 3, 3, 3).unwrap())
291            .unwrap();
292        c.rolling
293            .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2021, 3, 31, 4, 4, 4).unwrap())
294            .unwrap();
295        assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
296        c.verify_contains("123456789", 2);
297        c.verify_contains("0abcdefghijkl", 1);
298        c.verify_contains("ZZZ", 0);
299    }
300}
301// LCOV_EXCL_STOP