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 struct RollingFileAppenderBaseBuilder {
89 condition: RollingConditionBase,
90 filename: String,
91 max_filecount: usize,
92 current_filesize: u64,
93 writer_opt: Option<BufWriter<File>>,
94}
95
96impl Default for RollingFileAppenderBaseBuilder {
97 fn default() -> Self {
98 RollingFileAppenderBaseBuilder {
99 condition: RollingConditionBase::default(),
100 filename: String::new(),
101 max_filecount: 10,
102 current_filesize: 0,
103 writer_opt: None,
104 }
105 }
106}
107
108impl RollingFileAppenderBaseBuilder {
109 pub fn filename(mut self, filename: String) -> Self {
112 self.filename = filename;
113 self
114 }
115
116 pub fn max_filecount(mut self, max_filecount: usize) -> Self {
119 self.max_filecount = max_filecount;
120 self
121 }
122
123 pub fn condition_daily(mut self) -> Self {
125 self.condition.frequency_opt = Some(RollingFrequency::EveryDay);
126 self
127 }
128
129 pub fn condition_hourly(mut self) -> Self {
131 self.condition.frequency_opt = Some(RollingFrequency::EveryHour);
132 self
133 }
134
135 pub fn condition_minutely(mut self) -> Self {
137 self.condition.frequency_opt = Some(RollingFrequency::EveryMinute);
138 self
139 }
140
141 pub fn condition_max_file_size(mut self, x: u64) -> Self {
143 self.condition.max_size_opt = Some(x);
144 self
145 }
146
147 pub fn build(self) -> Result<RollingFileAppenderBase, &'static str> {
151 if self.filename.is_empty() {
152 return Err("A filename is required to be set and can not be blank");
153 }
154 Ok(RollingFileAppenderBase {
155 condition: self.condition,
156 filename: self.filename,
157 max_filecount: self.max_filecount,
158 current_filesize: self.current_filesize,
159 writer_opt: self.writer_opt,
160 })
161 }
162}
163
164impl RollingFileAppenderBase {
165 pub fn builder() -> RollingFileAppenderBaseBuilder {
168 RollingFileAppenderBaseBuilder::default()
169 }
170}
171
172#[cfg(feature = "non-blocking")]
173use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
174
175#[cfg(feature = "non-blocking")]
176use tracing_appender::non_blocking;
177
178#[cfg(feature = "non-blocking")]
179impl RollingFileAppenderBase {
180 pub fn get_non_blocking_appender(self) -> (NonBlocking, WorkerGuard) {
183 let (non_blocking, _guard) = non_blocking(self);
184 (non_blocking, _guard)
185 }
186}
187
188pub type RollingFileAppenderBase = RollingFileAppender<RollingConditionBase>;
190
191#[cfg(test)]
193mod test {
194 use super::*;
195
196 struct Context {
197 _tempdir: tempfile::TempDir,
198 rolling: RollingFileAppenderBase,
199 }
200
201 impl Context {
202 fn verify_contains(&mut self, needle: &str, n: usize) {
203 self.rolling.flush().unwrap();
204 let p = self.rolling.filename_for(n);
205 let haystack = fs::read_to_string(&p).unwrap();
206 if !haystack.contains(needle) {
207 panic!("file {:?} did not contain expected contents {}", p, needle);
208 }
209 }
210 }
211
212 fn build_context(condition: RollingConditionBase, max_files: usize) -> Context {
213 let tempdir = tempfile::tempdir().unwrap();
214 let filename = tempdir.path().join("test.log");
215 let rolling = RollingFileAppenderBase::new(filename, condition, max_files).unwrap();
216 Context {
217 _tempdir: tempdir,
218 rolling,
219 }
220 }
221
222 fn build_builder_context(mut builder: RollingFileAppenderBaseBuilder) -> Context {
223 if builder.filename.is_empty() {
224 builder = builder.filename(String::from("test.log"));
225 }
226 let tempdir = tempfile::tempdir().unwrap();
227 let filename = tempdir.path().join(&builder.filename);
228 builder = builder.filename(String::from(filename.as_os_str().to_str().unwrap()));
229 Context {
230 _tempdir: tempdir,
231 rolling: builder.build().unwrap(),
232 }
233 }
234
235 #[test]
236 fn frequency_every_day() {
237 let mut c = build_context(RollingConditionBase::new().daily(), 9);
238 c.rolling
239 .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
240 .unwrap();
241 c.rolling
242 .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
243 .unwrap();
244 c.rolling
245 .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 31, 1, 4, 0).unwrap())
246 .unwrap();
247 c.rolling
248 .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 5, 31, 1, 4, 0).unwrap())
249 .unwrap();
250 c.rolling
251 .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2022, 5, 31, 1, 4, 0).unwrap())
252 .unwrap();
253 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
254 c.verify_contains("Line 1", 3);
255 c.verify_contains("Line 2", 3);
256 c.verify_contains("Line 3", 2);
257 c.verify_contains("Line 4", 1);
258 c.verify_contains("Line 5", 0);
259 }
260
261 #[test]
262 fn frequency_every_day_limited_files() {
263 let mut c = build_context(RollingConditionBase::new().daily(), 2);
264 c.rolling
265 .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
266 .unwrap();
267 c.rolling
268 .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
269 .unwrap();
270 c.rolling
271 .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 31, 1, 4, 0).unwrap())
272 .unwrap();
273 c.rolling
274 .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 5, 31, 1, 4, 0).unwrap())
275 .unwrap();
276 c.rolling
277 .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2022, 5, 31, 1, 4, 0).unwrap())
278 .unwrap();
279 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
280 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
281 c.verify_contains("Line 3", 2);
282 c.verify_contains("Line 4", 1);
283 c.verify_contains("Line 5", 0);
284 }
285
286 #[test]
287 fn frequency_every_hour() {
288 let mut c = build_context(RollingConditionBase::new().hourly(), 9);
289 c.rolling
290 .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
291 .unwrap();
292 c.rolling
293 .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 2).unwrap())
294 .unwrap();
295 c.rolling
296 .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 30, 2, 1, 0).unwrap())
297 .unwrap();
298 c.rolling
299 .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 3, 31, 2, 1, 0).unwrap())
300 .unwrap();
301 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
302 c.verify_contains("Line 1", 2);
303 c.verify_contains("Line 2", 2);
304 c.verify_contains("Line 3", 1);
305 c.verify_contains("Line 4", 0);
306 }
307
308 #[test]
309 fn frequency_every_minute() {
310 let mut c = build_context(RollingConditionBase::new().frequency(RollingFrequency::EveryMinute), 9);
311 c.rolling
312 .write_with_datetime(b"Line 1\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
313 .unwrap();
314 c.rolling
315 .write_with_datetime(b"Line 2\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
316 .unwrap();
317 c.rolling
318 .write_with_datetime(b"Line 3\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 4).unwrap())
319 .unwrap();
320 c.rolling
321 .write_with_datetime(b"Line 4\n", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 0).unwrap())
322 .unwrap();
323 c.rolling
324 .write_with_datetime(b"Line 5\n", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 0).unwrap())
325 .unwrap();
326 c.rolling
327 .write_with_datetime(b"Line 6\n", &Local.with_ymd_and_hms(2022, 3, 30, 2, 3, 0).unwrap())
328 .unwrap();
329 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(4)).exists());
330 c.verify_contains("Line 1", 3);
331 c.verify_contains("Line 2", 3);
332 c.verify_contains("Line 3", 3);
333 c.verify_contains("Line 4", 2);
334 c.verify_contains("Line 5", 1);
335 c.verify_contains("Line 6", 0);
336 }
337
338 #[test]
339 fn max_size() {
340 let mut c = build_context(RollingConditionBase::new().max_size(10), 9);
341 c.rolling
342 .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
343 .unwrap();
344 c.rolling
345 .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 3).unwrap())
346 .unwrap();
347 c.rolling
348 .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
349 .unwrap();
350 c.rolling
351 .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
352 .unwrap();
353 c.rolling
354 .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2022, 3, 31, 1, 2, 3).unwrap())
355 .unwrap();
356 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
357 c.verify_contains("1234567890", 2);
358 c.verify_contains("abcdefghijkl", 1);
359 c.verify_contains("ZZZ", 0);
360 }
361
362 #[test]
363 fn max_size_existing() {
364 let mut c = build_context(RollingConditionBase::new().max_size(10), 9);
365 c.rolling
366 .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
367 .unwrap();
368 c.rolling.writer_opt.take();
371 c.rolling.current_filesize = 0;
372 c.rolling
373 .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 1, 3, 3).unwrap())
374 .unwrap();
375 c.rolling
376 .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
377 .unwrap();
378 c.rolling
379 .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
380 .unwrap();
381 c.rolling
382 .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2022, 3, 31, 1, 2, 3).unwrap())
383 .unwrap();
384 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
385 c.verify_contains("1234567890", 2);
386 c.verify_contains("abcdefghijkl", 1);
387 c.verify_contains("ZZZ", 0);
388 }
389
390 #[test]
391 fn daily_and_max_size() {
392 let mut c = build_context(RollingConditionBase::new().daily().max_size(10), 9);
393 c.rolling
394 .write_with_datetime(b"12345", &Local.with_ymd_and_hms(2021, 3, 30, 1, 2, 3).unwrap())
395 .unwrap();
396 c.rolling
397 .write_with_datetime(b"6789", &Local.with_ymd_and_hms(2021, 3, 30, 2, 3, 3).unwrap())
398 .unwrap();
399 c.rolling
400 .write_with_datetime(b"0", &Local.with_ymd_and_hms(2021, 3, 31, 2, 3, 3).unwrap())
401 .unwrap();
402 c.rolling
403 .write_with_datetime(b"abcdefghijkl", &Local.with_ymd_and_hms(2021, 3, 31, 3, 3, 3).unwrap())
404 .unwrap();
405 c.rolling
406 .write_with_datetime(b"ZZZ", &Local.with_ymd_and_hms(2021, 3, 31, 4, 4, 4).unwrap())
407 .unwrap();
408 assert!(!AsRef::<Path>::as_ref(&c.rolling.filename_for(3)).exists());
409 c.verify_contains("123456789", 2);
410 c.verify_contains("0abcdefghijkl", 1);
411 c.verify_contains("ZZZ", 0);
412 }
413
414 #[test]
415 fn rolling_file_appender_builder() {
416 let builder = RollingFileAppender::builder();
417
418 let builder = builder.condition_daily().condition_max_file_size(10);
419 let mut c = build_builder_context(builder);
420 c.rolling
421 .write_with_datetime(
422 b"abcdefghijklmnop",
423 &Local.with_ymd_and_hms(2021, 3, 31, 4, 4, 4).unwrap(),
424 )
425 .unwrap();
426 c.rolling
427 .write_with_datetime(b"12345678", &Local.with_ymd_and_hms(2021, 3, 31, 5, 4, 4).unwrap())
428 .unwrap();
429 assert!(AsRef::<Path>::as_ref(&c.rolling.filename_for(1)).exists());
430 assert!(Path::new(&c.rolling.filename_for(0)).exists());
431 c.verify_contains("abcdefghijklmnop", 1);
432 c.verify_contains("12345678", 0);
433 }
434
435 #[test]
436 fn rolling_file_appender_builder_no_filename() {
437 let builder = RollingFileAppender::builder();
438 let appender = builder.condition_daily().build();
439 assert!(appender.is_err());
440 }
441}
442