interstice_core/persistence/
log_rotation.rs1use std::fs;
7use std::io;
8use std::path::{Path, PathBuf};
9
10use crate::error::IntersticeError;
11
12#[derive(Debug, Clone)]
14pub struct RotationConfig {
15 pub max_log_size: u64,
17 pub max_rotated_logs: usize,
19}
20
21impl Default for RotationConfig {
22 fn default() -> Self {
23 Self {
24 max_log_size: 100 * 1024 * 1024, max_rotated_logs: 10,
26 }
27 }
28}
29
30pub struct LogRotator {
32 config: RotationConfig,
33}
34
35impl LogRotator {
36 pub fn new(config: RotationConfig) -> Self {
38 Self { config }
39 }
40
41 pub fn should_rotate(&self, log_path: &Path) -> Result<bool, IntersticeError> {
43 match fs::metadata(log_path) {
44 Ok(metadata) => Ok(metadata.len() > self.config.max_log_size),
45 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
46 Err(e) => Err(IntersticeError::Internal(format!(
47 "Shouold rotate log error: {}",
48 e
49 ))),
50 }
51 }
52
53 pub fn rotate(&self, log_path: &Path) -> Result<(), IntersticeError> {
60 if !log_path.exists() {
61 return Ok(()); }
63
64 let dir = log_path
65 .parent()
66 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid log path"))
67 .map_err(|err| {
68 IntersticeError::Internal(format!(
69 "Couldn't get transaction log file path: {}",
70 err
71 ))
72 })?;
73
74 let base_name_str = log_path
75 .file_name()
76 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "No filename"))
77 .map_err(|err| {
78 IntersticeError::Internal(format!(
79 "Couldn't retreive base name transaction log file: {}",
80 err
81 ))
82 })?
83 .to_string_lossy();
84
85 let mut max_num: i32 = -1;
87 for entry in fs::read_dir(dir).map_err(|err| {
88 IntersticeError::Internal(format!(
89 "Couldn't retreive the number of transaction log files: {}",
90 err
91 ))
92 })? {
93 let entry = entry.map_err(|err| {
94 IntersticeError::Internal(format!(
95 "Couldn't get transaction log file rotation: {}",
96 err
97 ))
98 })?;
99 let path = entry.path();
100 if let Some(name) = path.file_name() {
101 let name_str = name.to_string_lossy();
102 if let Some(num_str) = name_str.strip_prefix(&format!("{}.", base_name_str)) {
103 if let Ok(num) = num_str.parse::<i32>() {
104 max_num = max_num.max(num);
105 }
106 }
107 }
108 }
109
110 for i in (0..=max_num.max(-1)).rev() {
113 let new_num = i + 1;
114
115 if new_num as usize >= self.config.max_rotated_logs {
117 let path_to_remove = if i == -1 {
118 log_path.to_path_buf()
119 } else {
120 dir.join(format!("{}.{}", base_name_str, i))
121 };
122 if path_to_remove.exists() {
123 fs::remove_file(&path_to_remove).ok();
124 }
125 continue;
126 }
127
128 let old_path = if i == -1 {
129 log_path.to_path_buf()
130 } else {
131 dir.join(format!("{}.{}", base_name_str, i))
132 };
133
134 let new_path = dir.join(format!("{}.{}", base_name_str, new_num));
135
136 if old_path.exists() {
137 fs::rename(&old_path, &new_path).map_err(|err| {
138 IntersticeError::Internal(format!(
139 "Couldn't sync transaction log file to disk: {}",
140 err
141 ))
142 })?;
143 }
144 }
145
146 Ok(())
147 }
148
149 pub fn list_rotated_logs(&self, log_path: &Path) -> io::Result<Vec<PathBuf>> {
151 let dir = log_path
152 .parent()
153 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid log path"))?;
154
155 let base_name = log_path
156 .file_name()
157 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "No filename"))?
158 .to_string_lossy();
159
160 let mut logs = Vec::new();
161
162 for entry in fs::read_dir(dir)? {
163 let entry = entry?;
164 let path = entry.path();
165 if let Some(name) = path.file_name() {
166 let name_str = name.to_string_lossy();
167 if name_str.starts_with(&format!("{}.", base_name)) {
168 logs.push(path);
169 }
170 }
171 }
172
173 logs.sort();
174 Ok(logs)
175 }
176}