tracing_rolling_file/
lib.rs1#![deny(warnings)]
26
27use chrono::prelude::*;
28use std::{
29 convert::TryFrom,
30 fs::{self, File, OpenOptions},
31 io::{self, BufWriter, Write},
32 path::Path,
33};
34
35pub trait RollingCondition {
37 fn should_rollover(&mut self, now: &DateTime<Local>, current_filesize: u64) -> bool;
39}
40
41#[derive(Copy, Clone, Debug, Eq, PartialEq)]
43pub enum RollingFrequency {
44 EveryDay,
45 EveryHour,
46 EveryMinute,
47}
48
49impl RollingFrequency {
50 pub fn equivalent_datetime(&self, dt: &DateTime<Local>) -> DateTime<Local> {
53 let (year, month, day) = (dt.year(), dt.month(), dt.day());
54 let (hour, min, sec) = match self {
55 RollingFrequency::EveryDay => (0, 0, 0),
56 RollingFrequency::EveryHour => (dt.hour(), 0, 0),
57 RollingFrequency::EveryMinute => (dt.hour(), dt.minute(), 0),
58 };
59 Local.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap()
60 }
61}
62
63#[derive(Debug)]
68pub struct RollingFileAppender<RC>
69where
70 RC: RollingCondition,
71{
72 condition: RC,
73 filename: String,
74 max_filecount: usize,
75 current_filesize: u64,
76 writer_opt: Option<BufWriter<File>>,
77}
78
79impl<RC> RollingFileAppender<RC>
80where
81 RC: RollingCondition,
82{
83 pub fn new(filename: impl AsRef<Path>, condition: RC, max_filecount: usize) -> io::Result<RollingFileAppender<RC>> {
86 let filename = filename.as_ref().to_str().unwrap().to_string();
87 let mut appender = RollingFileAppender {
88 condition,
89 filename,
90 max_filecount,
91 current_filesize: 0,
92 writer_opt: None,
93 };
94 appender.open_writer_if_needed()?;
96 Ok(appender)
97 }
98
99 fn filename_for(&self, n: usize) -> String {
101 let f = self.filename.clone();
102 if n > 0 {
103 format!("{}.{}", f, n)
104 } else {
105 f
106 }
107 }
108
109 fn rotate_files(&mut self) -> io::Result<()> {
112 let _ = fs::remove_file(self.filename_for(self.max_filecount.max(1)));
114 let mut r = Ok(());
115 for i in (0..self.max_filecount.max(1)).rev() {
116 let rotate_from = self.filename_for(i);
117 let rotate_to = self.filename_for(i + 1);
118 if let Err(e) = fs::rename(&rotate_from, &rotate_to).or_else(|e| match e.kind() {
119 io::ErrorKind::NotFound => Ok(()),
120 _ => Err(e),
121 }) {
122 r = Err(e);
125 }
126 }
127 r
128 }
129
130 pub fn rollover(&mut self) -> io::Result<()> {
132 self.flush()?;
134 self.writer_opt.take();
136 self.current_filesize = 0;
137 self.rotate_files()?;
138 self.open_writer_if_needed()
139 }
140
141 fn open_writer_if_needed(&mut self) -> io::Result<()> {
143 if self.writer_opt.is_none() {
144 let path = self.filename_for(0);
145 let path = Path::new(&path);
146 let mut open_options = OpenOptions::new();
147 open_options.append(true).create(true);
148 let new_file = match open_options.open(path) {
149 Ok(new_file) => new_file,
150 Err(err) => {
151 let Some(parent) = path.parent() else {
152 return Err(err);
153 };
154 fs::create_dir_all(parent)?;
155 open_options.open(path)?
156 },
157 };
158 self.writer_opt = Some(BufWriter::new(new_file));
159 self.current_filesize = path.metadata().map_or(0, |m| m.len());
160 }
161 Ok(())
162 }
163
164 pub fn write_with_datetime(&mut self, buf: &[u8], now: &DateTime<Local>) -> io::Result<usize> {
166 if self.condition.should_rollover(now, self.current_filesize) {
167 if let Err(e) = self.rollover() {
168 eprintln!("WARNING: Failed to rotate logfile {}: {}", self.filename, e);
173 }
174 }
175 self.open_writer_if_needed()?;
176 if let Some(writer) = self.writer_opt.as_mut() {
177 let buf_len = buf.len();
178 writer.write_all(buf).map(|_| {
179 self.current_filesize += u64::try_from(buf_len).unwrap_or(u64::MAX);
180 buf_len
181 })
182 } else {
183 Err(io::Error::new(
184 io::ErrorKind::Other,
185 "unexpected condition: writer is missing",
186 ))
187 }
188 }
189}
190
191impl<RC> io::Write for RollingFileAppender<RC>
192where
193 RC: RollingCondition,
194{
195 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
196 let now = Local::now();
197 self.write_with_datetime(buf, &now)
198 }
199
200 fn flush(&mut self) -> io::Result<()> {
201 if let Some(writer) = self.writer_opt.as_mut() {
202 writer.flush()?;
203 }
204 Ok(())
205 }
206}
207
208pub mod base;
209pub use base::*;