prevent log file to become to big

This commit is contained in:
otsmr 2026-09-15 23:47:03 +02:00
parent 5914b81845
commit 4adade4ac8

View file

@ -5,6 +5,7 @@
use crate::bridge::logging::LogLevel; use crate::bridge::logging::LogLevel;
use crate::error::{Result, TwonlyError}; use crate::error::{Result, TwonlyError};
use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{Seek, Write as IoWrite}; use std::io::{Seek, Write as IoWrite};
@ -28,6 +29,9 @@ static TRACING_INIT: OnceLock<()> = OnceLock::new();
static APP_LOG: OnceLock<AppLog> = OnceLock::new(); static APP_LOG: OnceLock<AppLog> = OnceLock::new();
static RUST_LOG_IN_BACKGROUND: AtomicBool = AtomicBool::new(false); static RUST_LOG_IN_BACKGROUND: AtomicBool = AtomicBool::new(false);
const LOG_SIZE_CLEANUP_THRESHOLD_BYTES: usize = 5 * 1024 * 1024;
const LOG_SIZE_CLEANUP_TARGET_BYTES: usize = 4 * 1024 * 1024;
struct AppLog { struct AppLog {
path: PathBuf, path: PathBuf,
file: Mutex<File>, file: Mutex<File>,
@ -499,21 +503,58 @@ pub(crate) fn clean_log_file() -> Result<()> {
.is_some_and(|timestamp| timestamp > cutoff) .is_some_and(|timestamp| timestamp > cutoff)
}); });
let replacement = match keep_from { let mut replacement = match keep_from {
Some(0) => return Ok(()), Some(0) => Cow::Borrowed(contents.as_str()),
Some(index) => { Some(index) => {
let mut retained = contents.lines().skip(index).collect::<Vec<_>>().join("\n"); let mut retained = contents.lines().skip(index).collect::<Vec<_>>().join("\n");
if !retained.is_empty() { if !retained.is_empty() {
retained.push('\n'); retained.push('\n');
} }
retained Cow::Owned(retained)
} }
None => String::new(), None => Cow::Owned(String::new()),
}; };
if replacement.len() > LOG_SIZE_CLEANUP_THRESHOLD_BYTES {
replacement = Cow::Owned(retain_latest_log_bytes(
replacement.as_ref(),
LOG_SIZE_CLEANUP_TARGET_BYTES,
));
}
if let Cow::Owned(replacement) = replacement {
log.replace(&replacement)?; log.replace(&replacement)?;
}
Ok(()) Ok(())
} }
/// Keeps approximately `target_bytes` from the end while avoiding a partial
/// first log entry. The result can be slightly smaller by up to one log line.
fn retain_latest_log_bytes(contents: &str, target_bytes: usize) -> String {
if contents.len() <= target_bytes {
return contents.to_owned();
}
let estimated_start = contents.len() - target_bytes;
let bytes = contents.as_bytes();
let start = if estimated_start == 0 || bytes[estimated_start - 1] == b'\n' {
estimated_start
} else if let Some(offset) = bytes[estimated_start..]
.iter()
.position(|byte| *byte == b'\n')
{
estimated_start + offset + 1
} else {
let mut boundary = estimated_start;
while boundary < contents.len() && !contents.is_char_boundary(boundary) {
boundary += 1;
}
boundary
};
contents[start..].to_owned()
}
pub(crate) fn clear_log_file() -> Result<bool> { pub(crate) fn clear_log_file() -> Result<bool> {
let log = app_log()?; let log = app_log()?;
let had_contents = log let had_contents = log
@ -527,7 +568,10 @@ pub(crate) fn clear_log_file() -> Result<bool> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::AppLog; use super::{
retain_latest_log_bytes, AppLog, LOG_SIZE_CLEANUP_TARGET_BYTES,
LOG_SIZE_CLEANUP_THRESHOLD_BYTES,
};
#[test] #[test]
fn app_log_serializes_reads_and_truncation() { fn app_log_serializes_reads_and_truncation() {
@ -543,4 +587,25 @@ mod tests {
log.append(b"new\n").unwrap(); log.append(b"new\n").unwrap();
assert_eq!(std::fs::read_to_string(path).unwrap(), "retained\nnew\n"); assert_eq!(std::fs::read_to_string(path).unwrap(), "retained\nnew\n");
} }
#[test]
fn size_cleanup_keeps_the_latest_complete_log_lines() {
let contents = "oldest\nmiddle\nnewest\n";
assert_eq!(retain_latest_log_bytes(contents, 14), "middle\nnewest\n");
assert_eq!(retain_latest_log_bytes(contents, 12), "newest\n");
}
#[test]
fn size_cleanup_reduces_a_five_mib_log_to_about_four_mib() {
let line = format!("{}\n", "x".repeat(127));
let contents = line.repeat(LOG_SIZE_CLEANUP_THRESHOLD_BYTES / line.len() + 1);
assert!(contents.len() > LOG_SIZE_CLEANUP_THRESHOLD_BYTES);
let retained = retain_latest_log_bytes(&contents, LOG_SIZE_CLEANUP_TARGET_BYTES);
assert!(retained.len() <= LOG_SIZE_CLEANUP_TARGET_BYTES);
assert!(retained.len() > LOG_SIZE_CLEANUP_TARGET_BYTES - line.len());
assert!(retained.ends_with('\n'));
}
} }