feat(log): Add KeepSome rotation strategy (#677)

Co-authored-by: Krzysztof Krolak <krzysiek.krolak@gmail.com>
Co-authored-by: FabianLars <github@fabianlars.de>
v2
kris-ava 4 days ago committed by GitHub
parent 8b63de9dfe
commit 106e46ed51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,6 @@
---
log: minor
log-js: minor
---
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.

@ -31,11 +31,15 @@ thiserror = { workspace = true }
serde_repr = "0.1" serde_repr = "0.1"
byte-unit = "5" byte-unit = "5"
log = { workspace = true, features = ["kv_unstable"] } log = { workspace = true, features = ["kv_unstable"] }
time = { version = "0.3", features = ["formatting", "local-offset", "macros"] } time = { version = "0.3", features = [
"formatting",
"local-offset",
"macros",
"parsing",
] }
fern = "0.7" fern = "0.7"
tracing = { workspace = true, optional = true } tracing = { workspace = true, optional = true }
[target."cfg(target_os = \"android\")".dependencies] [target."cfg(target_os = \"android\")".dependencies]
android_logger = "0.15" android_logger = "0.15"

@ -47,6 +47,7 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [
Target::new(TargetKind::Stdout), Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: None }), Target::new(TargetKind::LogDir { file_name: None }),
]; ];
const LOG_DATE_FORMAT: &str = "[year]-[month]-[day]_[hour]-[minute]-[second]";
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -115,8 +116,12 @@ impl From<log::Level> for LogLevel {
} }
pub enum RotationStrategy { pub enum RotationStrategy {
/// Will keep all the logs, renaming them to include the date.
KeepAll, KeepAll,
/// Will only keep the most recent log up to its maximal size.
KeepOne, KeepOne,
/// Will keep some of the most recent logs, renaming them to include the date.
KeepSome(usize),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -577,6 +582,34 @@ pub fn attach_logger(
Ok(()) Ok(())
} }
fn rename_file_to_dated(
path: &impl AsRef<Path>,
dir: &impl AsRef<Path>,
file_name: &str,
timezone_strategy: &TimezoneStrategy,
) -> Result<(), Error> {
let to = dir.as_ref().join(format!(
"{}_{}.log",
file_name,
timezone_strategy
.get_now()
.format(&time::format_description::parse(LOG_DATE_FORMAT).unwrap())
.unwrap(),
));
if to.is_file() {
// designated rotated log file name already exists
// highly unlikely but defensively handle anyway by adding .bak to filename
let mut to_bak = to.clone();
to_bak.set_file_name(format!(
"{}.bak",
to_bak.file_name().unwrap().to_string_lossy()
));
fs::rename(&to, to_bak)?;
}
fs::rename(path, to)?;
Ok(())
}
fn get_log_file_path( fn get_log_file_path(
dir: &impl AsRef<Path>, dir: &impl AsRef<Path>,
file_name: &str, file_name: &str,
@ -591,27 +624,37 @@ fn get_log_file_path(
if log_size > max_file_size { if log_size > max_file_size {
match rotation_strategy { match rotation_strategy {
RotationStrategy::KeepAll => { RotationStrategy::KeepAll => {
let to = dir.as_ref().join(format!( rename_file_to_dated(&path, dir, file_name, timezone_strategy)?;
"{}_{}.log", }
file_name, RotationStrategy::KeepSome(how_many) => {
timezone_strategy.get_now().format(&format_description!( let mut files = fs::read_dir(dir)?
"[year]-[month]-[day]_[hour]-[minute]-[second]" .filter_map(|entry| {
))?, let entry = entry.ok()?;
)); let path = entry.path();
if to.is_file() { let old_file_name = path.file_name()?.to_string_lossy().into_owned();
// designated rotated log file name already exists if old_file_name.starts_with(file_name) {
// highly unlikely but defensively handle anyway by adding .bak to filename let date = old_file_name
let mut to_bak = to.clone(); .strip_prefix(file_name)?
to_bak.set_file_name(format!( .strip_prefix("_")?
"{}.bak", .strip_suffix(".log")?;
to_bak Some((path, date.to_string()))
.file_name() } else {
.map(|f| f.to_string_lossy()) None
.unwrap_or_default() }
)); })
fs::rename(&to, to_bak)?; .collect::<Vec<_>>();
// Regular sorting, so the oldest files are first. Lexicographical
// sorting is fine due to the date format.
files.sort_by(|a, b| a.1.cmp(&b.1));
// We want to make space for the file we will be soon renaming, AND
// the file we will be creating. Thus we need to keep how_many - 2 files.
if files.len() > (*how_many - 2) {
files.truncate(files.len() + 2 - *how_many);
for (old_log_path, _) in files {
fs::remove_file(old_log_path)?;
}
} }
fs::rename(&path, to)?; rename_file_to_dated(&path, dir, file_name, timezone_strategy)?;
} }
RotationStrategy::KeepOne => { RotationStrategy::KeepOne => {
fs::remove_file(&path)?; fs::remove_file(&path)?;
@ -619,6 +662,5 @@ fn get_log_file_path(
} }
} }
} }
Ok(path) Ok(path)
} }

Loading…
Cancel
Save