Skip to content

Commit

Permalink
extra checks for multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
maximevanhees committed Oct 21, 2024
1 parent 7d1476a commit bc70445
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 15 deletions.
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ clap = "2.33"
git-version = "0.3.5"
command-group = "1.0.8"
chrono = "0.4.38"
regex = "1.11.0"
98 changes: 87 additions & 11 deletions src/manager/filelogger.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
use std::fs::metadata;

Check warning on line 1 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `std::fs::metadata`

Check warning on line 1 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `std::fs::metadata`

Check warning on line 1 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `std::fs::metadata`
use std::path::PathBuf;

use anyhow::{Context, Result};
use chrono::Local;
use regex::Regex;
use tokio::fs::{File, OpenOptions};
use tokio::io::AsyncWriteExt;
use tokio::sync::futures;

Check warning on line 9 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `tokio::sync::futures`

Check warning on line 9 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `tokio::sync::futures`

Check warning on line 9 in src/manager/filelogger.rs

View workflow job for this annotation

GitHub Actions / build

unused import: `tokio::sync::futures`

pub struct RotatingFileLogger {
file_path: String,
file_path: PathBuf,
size_threshold: u64,
current_size: u64,
file: File,
rotated_file_pattern: Regex,
max_rotated_files: usize,
}

impl RotatingFileLogger {
pub async fn new(file_path: impl Into<String>, size_threshold: u64) -> Result<Self> {
pub async fn new<P>(file_path: P, size_threshold: u64) -> Result<Self>
where
P: Into<PathBuf>,
{
let file_path = file_path.into();

// Open or create the log file
Expand All @@ -20,17 +30,33 @@ impl RotatingFileLogger {
.append(true)
.open(&file_path)
.await
.with_context(|| format!("Failed to open log file: {}", file_path))?;
.with_context(|| format!("Failed to open log file: {:?}", file_path))?;

// Get curretn file size
let metadata = file.metadata().await?;
let current_size = metadata.len();

// Compile regex to match rotated log files
// Example: log_20230826_123456.txt
let base_name = file_path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.trim_end_matches(".txt");

let pattern = format!(r"^{}_\d{{8}}_\d{{6}}\.txt$", regex::escape(base_name));
info!("pattern: {:?}", pattern);

let rotated_file_pattern = Regex::new(&pattern)
.with_context(|| format!("Failed to compile regex pattern: {}", pattern))?;

Ok(RotatingFileLogger {
file_path,
size_threshold,
current_size,
file,
rotated_file_pattern,
max_rotated_files: 5,
})
}

Expand All @@ -55,27 +81,77 @@ impl RotatingFileLogger {
// Generate new filename with date and time
let now = Local::now();
let datetime_str = now.format("%Y%m%d_%H%M%S").to_string();
let rotated_file_name = format!(
"{}_{}.txt",
self.file_path.trim_end_matches(".txt"),
datetime_str
);
let base_stem = self
.file_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("")
.trim_end_matches(".txt");

let rotated_file_name = format!("{}_{}.txt", base_stem, datetime_str);
let rotated_file_path = self.file_path.with_file_name(rotated_file_name);

// Rename the current file to teh rotated filename
tokio::fs::rename(&self.file_path, &rotated_file_name)
tokio::fs::rename(&self.file_path, &rotated_file_path)
.await
.with_context(|| format!("Failed to rename log file to {}", rotated_file_name))?;
.with_context(|| format!("Failed to rename log file to {:?}", rotated_file_path))?;

// Open a new file with the original name
self.file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.file_path)
.await
.with_context(|| format!("Failed to open new log file: {}", self.file_path))?;
.with_context(|| format!("Failed to open new log file: {:?}", self.file_path))?;

self.current_size = 0;

// Enforce the maximum number of rotated files
self.enforce_max_rotated_files().await?;

Ok(())
}

async fn enforce_max_rotated_files(&self) -> Result<()> {
let dir = self
.file_path
.parent()
.ok_or_else(|| anyhow::anyhow!("Failed to get log file directory"))?;

let mut entries = tokio::fs::read_dir(dir).await?;
let mut rotated_files = Vec::new();

while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_file() {
if let Some(filename) = path.file_name().and_then(|s| s.to_str()) {
info!("Found related logfile: {:?}", filename);
if self.rotated_file_pattern.is_match(filename) {
info!("...and matched the pattern");
// retrieve modification time
if let Ok(metadata) = tokio::fs::metadata(&path).await {
if let Ok(modified) = metadata.modified() {
rotated_files.push((path, modified));
}
}
}
}
}
}
// Sort by creation time
rotated_files.sort_by_key(|(_, modtime)| *modtime);

// If there are more than max_rotated_files, delete the oldest ones
if rotated_files.len() > self.max_rotated_files {
let files_to_delete = &rotated_files[..rotated_files.len() - self.max_rotated_files];
for (file, _) in files_to_delete {
tokio::fs::remove_file(file).await.with_context(|| {
format!("Failed to delete old rotated log file: {:?}", file)
})?;
info!("removed file: {:?}", file);
}
}

Ok(())
}
}
4 changes: 2 additions & 2 deletions src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod buffer;
mod filelogger;
pub use buffer::Logs;

const MAX_LOG_FILE_SIZE: u64 = 1 * 1024 * 1024; // 1 MiB
const MAX_LOG_FILE_SIZE: u64 = 3 * 1024; // * 1024; // 1 MiB

pub struct Process {
cmd: String,
Expand Down Expand Up @@ -199,7 +199,7 @@ impl ProcessManager {
}
Log::File(log_file_path) => {
let logger = Arc::new(Mutex::new(
RotatingFileLogger::new(&log_file_path, MAX_LOG_FILE_SIZE).await?,
RotatingFileLogger::new(log_file_path.clone(), MAX_LOG_FILE_SIZE).await?,
));
self.log_service_output(&log_file_path, Some(logger), &mut child)
.await;
Expand Down
3 changes: 3 additions & 0 deletions src/zinit/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ impl Service {
if self.log_file.is_none() {
bail!("log_file must be specified when log is set to 'file'");
}
if !(self.log_file.clone().unwrap().ends_with(".txt")) {
bail!("log_file must have .txt extension");
}
}

Signal::from_str(&self.signal.stop.to_uppercase())?;
Expand Down

0 comments on commit bc70445

Please sign in to comment.