Skip to content

Commit 106e46e

Browse files
kris-avakrzykro2FabianLars
authored
feat(log): Add KeepSome rotation strategy (#677)
Co-authored-by: Krzysztof Krolak <[email protected]> Co-authored-by: FabianLars <[email protected]>
1 parent 8b63de9 commit 106e46e

File tree

3 files changed

+75
-23
lines changed

3 files changed

+75
-23
lines changed

.changes/log-keep-some.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
log: minor
3+
log-js: minor
4+
---
5+
6+
Added the `KeepSome` rotation strategy. Like `KeepAll` it will rename files when the max file size is exceeded but will keep only the specified amount of files around.

plugins/log/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ thiserror = { workspace = true }
3131
serde_repr = "0.1"
3232
byte-unit = "5"
3333
log = { workspace = true, features = ["kv_unstable"] }
34-
time = { version = "0.3", features = ["formatting", "local-offset", "macros"] }
34+
time = { version = "0.3", features = [
35+
"formatting",
36+
"local-offset",
37+
"macros",
38+
"parsing",
39+
] }
3540
fern = "0.7"
3641
tracing = { workspace = true, optional = true }
3742

38-
3943
[target."cfg(target_os = \"android\")".dependencies]
4044
android_logger = "0.15"
4145

plugins/log/src/lib.rs

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [
4747
Target::new(TargetKind::Stdout),
4848
Target::new(TargetKind::LogDir { file_name: None }),
4949
];
50+
const LOG_DATE_FORMAT: &str = "[year]-[month]-[day]_[hour]-[minute]-[second]";
5051

5152
#[derive(Debug, thiserror::Error)]
5253
pub enum Error {
@@ -115,8 +116,12 @@ impl From<log::Level> for LogLevel {
115116
}
116117

117118
pub enum RotationStrategy {
119+
/// Will keep all the logs, renaming them to include the date.
118120
KeepAll,
121+
/// Will only keep the most recent log up to its maximal size.
119122
KeepOne,
123+
/// Will keep some of the most recent logs, renaming them to include the date.
124+
KeepSome(usize),
120125
}
121126

122127
#[derive(Debug, Clone)]
@@ -577,6 +582,34 @@ pub fn attach_logger(
577582
Ok(())
578583
}
579584

585+
fn rename_file_to_dated(
586+
path: &impl AsRef<Path>,
587+
dir: &impl AsRef<Path>,
588+
file_name: &str,
589+
timezone_strategy: &TimezoneStrategy,
590+
) -> Result<(), Error> {
591+
let to = dir.as_ref().join(format!(
592+
"{}_{}.log",
593+
file_name,
594+
timezone_strategy
595+
.get_now()
596+
.format(&time::format_description::parse(LOG_DATE_FORMAT).unwrap())
597+
.unwrap(),
598+
));
599+
if to.is_file() {
600+
// designated rotated log file name already exists
601+
// highly unlikely but defensively handle anyway by adding .bak to filename
602+
let mut to_bak = to.clone();
603+
to_bak.set_file_name(format!(
604+
"{}.bak",
605+
to_bak.file_name().unwrap().to_string_lossy()
606+
));
607+
fs::rename(&to, to_bak)?;
608+
}
609+
fs::rename(path, to)?;
610+
Ok(())
611+
}
612+
580613
fn get_log_file_path(
581614
dir: &impl AsRef<Path>,
582615
file_name: &str,
@@ -591,34 +624,43 @@ fn get_log_file_path(
591624
if log_size > max_file_size {
592625
match rotation_strategy {
593626
RotationStrategy::KeepAll => {
594-
let to = dir.as_ref().join(format!(
595-
"{}_{}.log",
596-
file_name,
597-
timezone_strategy.get_now().format(&format_description!(
598-
"[year]-[month]-[day]_[hour]-[minute]-[second]"
599-
))?,
600-
));
601-
if to.is_file() {
602-
// designated rotated log file name already exists
603-
// highly unlikely but defensively handle anyway by adding .bak to filename
604-
let mut to_bak = to.clone();
605-
to_bak.set_file_name(format!(
606-
"{}.bak",
607-
to_bak
608-
.file_name()
609-
.map(|f| f.to_string_lossy())
610-
.unwrap_or_default()
611-
));
612-
fs::rename(&to, to_bak)?;
627+
rename_file_to_dated(&path, dir, file_name, timezone_strategy)?;
628+
}
629+
RotationStrategy::KeepSome(how_many) => {
630+
let mut files = fs::read_dir(dir)?
631+
.filter_map(|entry| {
632+
let entry = entry.ok()?;
633+
let path = entry.path();
634+
let old_file_name = path.file_name()?.to_string_lossy().into_owned();
635+
if old_file_name.starts_with(file_name) {
636+
let date = old_file_name
637+
.strip_prefix(file_name)?
638+
.strip_prefix("_")?
639+
.strip_suffix(".log")?;
640+
Some((path, date.to_string()))
641+
} else {
642+
None
643+
}
644+
})
645+
.collect::<Vec<_>>();
646+
// Regular sorting, so the oldest files are first. Lexicographical
647+
// sorting is fine due to the date format.
648+
files.sort_by(|a, b| a.1.cmp(&b.1));
649+
// We want to make space for the file we will be soon renaming, AND
650+
// the file we will be creating. Thus we need to keep how_many - 2 files.
651+
if files.len() > (*how_many - 2) {
652+
files.truncate(files.len() + 2 - *how_many);
653+
for (old_log_path, _) in files {
654+
fs::remove_file(old_log_path)?;
655+
}
613656
}
614-
fs::rename(&path, to)?;
657+
rename_file_to_dated(&path, dir, file_name, timezone_strategy)?;
615658
}
616659
RotationStrategy::KeepOne => {
617660
fs::remove_file(&path)?;
618661
}
619662
}
620663
}
621664
}
622-
623665
Ok(path)
624666
}

0 commit comments

Comments
 (0)