1use 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 pub fn new() -> RollingConditionBase {
24 RollingConditionBase {
25 last_write_opt: None,
26 frequency_opt: None,
27 max_size_opt: None,
28 }
29 }
30
31 pub fn frequency(mut self, x: RollingFrequency) -> RollingConditionBase {
33 self.frequency_opt = Some(x);
34 self
35 }
36
37 pub fn daily(mut self) -> RollingConditionBase {
39 self.frequency_opt = Some(RollingFrequency::EveryDay);
40 self
41 }
42
43 pub fn hourly(mut self) -> RollingConditionBase {
45 self.frequency_opt = Some(RollingFrequency::EveryHour);
46 self
47 }
48
49 pub fn minutely(mut self) -> RollingConditionBase {
51 self.frequency_opt = Some(RollingFrequency::EveryMinute);
52 self
53 }
54
55 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
88pub type RollingFileAppenderBase = RollingFileAppender<RollingConditionBase>;
90
91#[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 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