Skip to main content

baichun_framework_logger/appender/
rolling.rs

1use std::{
2    fs::{self, File},
3    io::{BufWriter, Write},
4    path::{Path, PathBuf},
5    sync::Arc,
6    time::SystemTime,
7};
8
9use chrono::{DateTime, Datelike, Local};
10use parking_lot::Mutex;
11
12use crate::{
13    config::{LogLevel, SizeRollingConfig, TimeRollingConfig},
14    error::Result,
15};
16
17use super::LogAppender;
18
19/// 滚动策略
20#[derive(Debug, Clone)]
21pub enum RollingStrategy {
22    /// 按时间滚动
23    Time(TimeRollingConfig),
24    /// 按大小滚动
25    Size(SizeRollingConfig),
26}
27
28/// 滚动文件追加器
29#[derive(Debug)]
30pub struct RollingFileAppender {
31    /// 基础路径
32    base_path: PathBuf,
33    /// 当前文件路径
34    current_path: Arc<Mutex<PathBuf>>,
35    /// 当前写入器
36    writer: Arc<Mutex<BufWriter<File>>>,
37    /// 滚动策略
38    strategy: RollingStrategy,
39    /// 最后检查时间
40    last_check: Arc<Mutex<SystemTime>>,
41    /// 当前文件大小
42    current_size: Arc<Mutex<u64>>,
43}
44
45impl RollingFileAppender {
46    /// 创建滚动文件追加器
47    pub fn new(base_path: PathBuf, strategy: RollingStrategy) -> Result<Self> {
48        // 创建目录
49        if let Some(parent) = base_path.parent() {
50            fs::create_dir_all(parent)?;
51        }
52
53        // 获取当前文件路径
54        let current_path = Self::get_current_path(&base_path, &strategy)?;
55
56        // 确保目标目录存在
57        if let Some(parent) = current_path.parent() {
58            fs::create_dir_all(parent)?;
59        }
60
61        // 打开文件
62        let file = Self::open_file(&current_path)?;
63        let current_size = file.metadata()?.len();
64
65        Ok(Self {
66            base_path,
67            current_path: Arc::new(Mutex::new(current_path)),
68            writer: Arc::new(Mutex::new(BufWriter::new(file))),
69            strategy,
70            last_check: Arc::new(Mutex::new(SystemTime::now())),
71            current_size: Arc::new(Mutex::new(current_size)),
72        })
73    }
74
75    /// 获取当前文件路径
76    fn get_current_path(base_path: &Path, strategy: &RollingStrategy) -> Result<PathBuf> {
77        match strategy {
78            RollingStrategy::Time(config) => {
79                let now = Local::now();
80                let suffix = match config.period {
81                    crate::config::TimePeriod::Hourly => now.format("%Y%m%d%H"),
82                    crate::config::TimePeriod::Daily => now.format("%Y%m%d"),
83                    crate::config::TimePeriod::Weekly => now.format("%Y%V"),
84                    crate::config::TimePeriod::Monthly => now.format("%Y%m"),
85                };
86                let mut path = base_path.to_path_buf();
87                if let Some(ext) = path.extension() {
88                    let mut new_ext = ext.to_string_lossy().to_string();
89                    new_ext.push('.');
90                    new_ext.push_str(&suffix.to_string());
91                    path.set_extension(new_ext);
92                } else {
93                    path.set_extension(suffix.to_string());
94                }
95                Ok(path)
96            }
97            RollingStrategy::Size(_) => Ok(base_path.to_path_buf()),
98        }
99    }
100
101    /// 打开文件
102    fn open_file(path: &Path) -> Result<File> {
103        // 确保目录存在
104        if let Some(parent) = path.parent() {
105            fs::create_dir_all(parent)?;
106        }
107
108        Ok(std::fs::OpenOptions::new()
109            .create(true)
110            .write(true)
111            .append(true)
112            .open(path)?)
113    }
114
115    /// 检查是否需要滚动
116    fn check_roll(&self) -> Result<bool> {
117        match &self.strategy {
118            RollingStrategy::Time(config) => {
119                let now = SystemTime::now();
120                let last_check = *self.last_check.lock();
121                let duration = now.duration_since(last_check).unwrap();
122
123                let should_roll = match config.period {
124                    crate::config::TimePeriod::Hourly => duration.as_secs() >= 3600,
125                    crate::config::TimePeriod::Daily => duration.as_secs() >= 86400,
126                    crate::config::TimePeriod::Weekly => duration.as_secs() >= 604800,
127                    crate::config::TimePeriod::Monthly => {
128                        let now: DateTime<Local> = now.into();
129                        let last: DateTime<Local> = last_check.into();
130                        now.month() != last.month()
131                    }
132                };
133
134                if should_roll {
135                    *self.last_check.lock() = now;
136                }
137
138                Ok(should_roll)
139            }
140            RollingStrategy::Size(config) => {
141                let current_size = *self.current_size.lock();
142                Ok(current_size >= config.max_size)
143            }
144        }
145    }
146
147    /// 执行滚动
148    fn roll(&self) -> Result<()> {
149        // 获取新的文件路径
150        let new_path = Self::get_current_path(&self.base_path, &self.strategy)?;
151
152        // 创建新文件
153        let new_file = Self::open_file(&new_path)?;
154
155        // 更新状态
156        {
157            let mut current_path = self.current_path.lock();
158            let mut writer = self.writer.lock();
159            let mut current_size = self.current_size.lock();
160
161            // 刷新旧文件
162            writer.flush()?;
163
164            // 更新路径
165            *current_path = new_path;
166
167            // 更新写入器
168            *writer = BufWriter::new(new_file);
169
170            // 重置大小
171            *current_size = 0;
172        }
173
174        // 清理旧文件
175        self.cleanup()?;
176
177        Ok(())
178    }
179
180    /// 清理旧文件
181    fn cleanup(&self) -> Result<()> {
182        match &self.strategy {
183            RollingStrategy::Time(config) => {
184                let now = Local::now();
185                let keep_days = config.keep_days;
186
187                let dir = self.base_path.parent().unwrap_or_else(|| Path::new("."));
188                let prefix = self
189                    .base_path
190                    .file_name()
191                    .unwrap()
192                    .to_string_lossy()
193                    .to_string();
194
195                for entry in fs::read_dir(dir)? {
196                    let entry = entry?;
197                    let path = entry.path();
198
199                    // 检查文件名前缀
200                    if let Some(name) = path.file_name() {
201                        let name = name.to_string_lossy();
202                        if !name.starts_with(&prefix) {
203                            continue;
204                        }
205                    }
206
207                    // 获取文件修改时间
208                    let metadata = fs::metadata(&path)?;
209                    let modified: DateTime<Local> = metadata.modified()?.into();
210
211                    // 检查是否超过保留天数
212                    let days_old = now.signed_duration_since(modified).num_days();
213                    if days_old > keep_days as i64 {
214                        fs::remove_file(path)?;
215                    }
216                }
217            }
218            RollingStrategy::Size(config) => {
219                let dir = self.base_path.parent().unwrap_or_else(|| Path::new("."));
220                let prefix = self
221                    .base_path
222                    .file_name()
223                    .unwrap()
224                    .to_string_lossy()
225                    .to_string();
226
227                let mut files: Vec<_> = fs::read_dir(dir)?
228                    .filter_map(|e| e.ok())
229                    .filter(|e| {
230                        if let Some(name) = e.path().file_name() {
231                            name.to_string_lossy().starts_with(&prefix)
232                        } else {
233                            false
234                        }
235                    })
236                    .collect();
237
238                if files.len() > config.max_files as usize {
239                    // 按修改时间排序
240                    files.sort_by_key(|f| f.metadata().unwrap().modified().unwrap());
241
242                    // 删除最旧的文件
243                    for file in files.iter().take(files.len() - config.max_files as usize) {
244                        fs::remove_file(file.path())?;
245                    }
246                }
247            }
248        }
249
250        Ok(())
251    }
252}
253
254impl LogAppender for RollingFileAppender {
255    fn write(&self, _level: LogLevel, message: &str) -> Result<()> {
256        // 检查是否需要滚动
257        if self.check_roll()? {
258            self.roll()?;
259        }
260
261        // 写入日志
262        let mut writer = self.writer.lock();
263        writer.write_all(message.as_bytes())?;
264        writer.write_all(b"\n")?;
265
266        // 更新文件大小
267        *self.current_size.lock() += message.len() as u64 + 1;
268
269        Ok(())
270    }
271
272    fn flush(&self) -> Result<()> {
273        let mut writer = self.writer.lock();
274        writer.flush()?;
275        Ok(())
276    }
277}
278
279impl Clone for RollingFileAppender {
280    fn clone(&self) -> Self {
281        Self {
282            base_path: self.base_path.clone(),
283            current_path: Arc::clone(&self.current_path),
284            writer: Arc::clone(&self.writer),
285            strategy: self.strategy.clone(),
286            last_check: Arc::clone(&self.last_check),
287            current_size: Arc::clone(&self.current_size),
288        }
289    }
290}