mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
prevent log file to become to big
This commit is contained in:
parent
5914b81845
commit
4adade4ac8
1 changed files with 71 additions and 6 deletions
|
|
@ -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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue