Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/log-keep-some.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 6 additions & 2 deletions plugins/log/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ thiserror = { workspace = true }
serde_repr = "0.1"
byte-unit = "5"
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"
tracing = { workspace = true, optional = true }


[target."cfg(target_os = \"android\")".dependencies]
android_logger = "0.15"

Expand Down
84 changes: 63 additions & 21 deletions plugins/log/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [
Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: None }),
];
const LOG_DATE_FORMAT: &str = "[year]-[month]-[day]_[hour]-[minute]-[second]";

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

pub enum RotationStrategy {
/// Will keep all the logs, renaming them to include the date.
KeepAll,
/// Will only keep the most recent log up to its maximal size.
KeepOne,
/// Will keep some of the most recent logs, renaming them to include the date.
KeepSome(usize),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -577,6 +582,34 @@ pub fn attach_logger(
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(
dir: &impl AsRef<Path>,
file_name: &str,
Expand All @@ -591,34 +624,43 @@ fn get_log_file_path(
if log_size > max_file_size {
match rotation_strategy {
RotationStrategy::KeepAll => {
let to = dir.as_ref().join(format!(
"{}_{}.log",
file_name,
timezone_strategy.get_now().format(&format_description!(
"[year]-[month]-[day]_[hour]-[minute]-[second]"
))?,
));
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()
.map(|f| f.to_string_lossy())
.unwrap_or_default()
));
fs::rename(&to, to_bak)?;
rename_file_to_dated(&path, dir, file_name, timezone_strategy)?;
}
RotationStrategy::KeepSome(how_many) => {
let mut files = fs::read_dir(dir)?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
let old_file_name = path.file_name()?.to_string_lossy().into_owned();
if old_file_name.starts_with(file_name) {
let date = old_file_name
.strip_prefix(file_name)?
.strip_prefix("_")?
.strip_suffix(".log")?;
Some((path, date.to_string()))
} else {
None
}
})
.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 => {
fs::remove_file(&path)?;
}
}
}
}

Ok(path)
}
Loading