-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcleanup.rs
More file actions
110 lines (90 loc) · 3.91 KB
/
cleanup.rs
File metadata and controls
110 lines (90 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use log::error;
use log::info;
use shvrpc::journalrw::datetime_to_log3_filename;
use tokio::fs;
use tokio::io;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::util::msec_to_log2_filename;
#[derive(Debug, Clone)]
pub(crate) struct LogFile {
pub(crate) name: PathBuf,
pub(crate) parent_dir: PathBuf,
pub(crate) size: u64,
}
pub(crate) async fn collect_log_files(dir: impl AsRef<Path>) -> io::Result<Vec<LogFile>> {
let mut result = Vec::new();
let mut dirs = vec![dir.as_ref().to_path_buf()];
while let Some(current_dir) = dirs.pop() {
let mut entries = fs::read_dir(¤t_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
let metadata = entry.metadata().await?;
if metadata.is_dir() {
dirs.push(path);
} else if metadata.is_file() && path.to_str().is_some_and(|p| p.ends_with(".log2") || p.ends_with(".log3"))
&& let (Some(file_name), Some(parent)) = (path.file_name(), path.parent()) {
result.push(LogFile {
name: file_name.into(),
parent_dir: parent.to_path_buf(),
size: metadata.len(),
});
}
}
}
Ok(result)
}
/// Prune `.log[2,3]` files while keeping the newest one per directory
pub(crate) async fn cleanup_log_files(dir: impl AsRef<Path>, size_limit: u64, days_to_keep: i64) -> io::Result<()> {
let files = collect_log_files(dir).await?;
let mut files_size: u64 = files.iter().map(|f| f.size).sum();
info!("log files size: {files_size}, size limit: {size_limit}, days_to_keep: {days_to_keep}");
if files_size < size_limit {
info!("Size limit not hit, nothing to cleanup");
return Ok(());
}
// Group by parent directory
let mut grouped: HashMap<PathBuf, Vec<LogFile>> = HashMap::new();
for file in files {
grouped.entry(file.parent_dir.clone()).or_default().push(file);
}
let mut deletable_files = Vec::new();
// This file doesn't have to exist, I'm only constructing the filename for log retention.
let oldest_log2_file_to_keep = PathBuf::from(msec_to_log2_filename(shvproto::DateTime::now().add_days(-days_to_keep).epoch_msec()));
let oldest_log3_file_to_keep = PathBuf::from(datetime_to_log3_filename(shvproto::DateTime::now().add_days(-days_to_keep)));
info!("keeping files younger than {filename_log2} or {filename_log3}",
filename_log2 = oldest_log2_file_to_keep.to_string_lossy(),
filename_log3 = oldest_log3_file_to_keep.to_string_lossy(),
);
for (_dir, mut group) in grouped {
// Sort descending (newest first)
group.sort_by(|a, b| b.name.cmp(&a.name));
// Keep the newest two files (for snapshots), and keep files newer than oldest_file_to_delete
deletable_files.extend(group
.into_iter()
.skip(2)
.filter(|log_file| if log_file.name.to_string_lossy().ends_with(".log2") {
log_file.name < oldest_log2_file_to_keep
} else {
log_file.name < oldest_log3_file_to_keep
})
);
}
// Sort deletable files (oldest first for deletion)
deletable_files.sort_by(|a, b| a.name.cmp(&b.name));
let files_size_orig = files_size;
for file in deletable_files {
if files_size <= size_limit {
break;
}
let file_path = file.parent_dir.join(file.name);
if let Err(err) = fs::remove_file(&file_path).await {
error!("Cannot delete {path}: {err}", path = file_path.to_string_lossy());
continue;
}
files_size -= file.size;
info!("Deleted: {path} ({size} bytes)", path = file_path.to_string_lossy(), size = file.size);
}
info!("Cleaned up size: {cleaned_up}", cleaned_up = files_size_orig - files_size);
Ok(())
}