8cd379ca33
Co-authored-by: Douwe Osinga <douwe@squareup.com>
171 lines
5.3 KiB
Rust
171 lines
5.3 KiB
Rust
use crate::config::paths::Paths;
|
|
use anyhow::{Context, Result};
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
/// Returns the directory where log files should be stored for a specific component.
|
|
/// Creates the directory structure if it doesn't exist.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `component` - The component name (e.g., "cli", "server", "debug", "llm")
|
|
/// * `use_date_subdir` - Whether to create a date-based subdirectory
|
|
pub fn prepare_log_directory(component: &str, use_date_subdir: bool) -> Result<PathBuf> {
|
|
let base_log_dir = Paths::in_state_dir("logs");
|
|
|
|
let _ = cleanup_old_logs(component);
|
|
|
|
let component_dir = base_log_dir.join(component);
|
|
|
|
let log_dir = if use_date_subdir {
|
|
component_dir.join(chrono::Local::now().format("%Y-%m-%d").to_string())
|
|
} else {
|
|
component_dir
|
|
};
|
|
|
|
fs::create_dir_all(&log_dir)
|
|
.with_context(|| format!("Failed to create log directory: {:?}", log_dir))?;
|
|
|
|
Ok(log_dir)
|
|
}
|
|
|
|
pub fn cleanup_old_logs(component: &str) -> Result<()> {
|
|
let base_log_dir = Paths::in_state_dir("logs");
|
|
let component_dir = base_log_dir.join(component);
|
|
|
|
if !component_dir.exists() {
|
|
return Ok(());
|
|
}
|
|
|
|
let two_weeks = SystemTime::now() - Duration::from_secs(14 * 24 * 60 * 60);
|
|
let entries = fs::read_dir(&component_dir)?;
|
|
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
|
|
if let Ok(metadata) = entry.metadata() {
|
|
if let Ok(modified) = metadata.modified() {
|
|
if modified < two_weeks && path.is_dir() {
|
|
let _ = fs::remove_dir_all(&path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs;
|
|
|
|
#[test]
|
|
fn test_get_log_directory_basic_functionality() {
|
|
// Test basic directory creation without date subdirectory
|
|
let result = prepare_log_directory("cli", false);
|
|
assert!(result.is_ok());
|
|
|
|
let log_dir = result.unwrap();
|
|
|
|
// Verify the directory was created and has correct structure
|
|
assert!(log_dir.exists());
|
|
assert!(log_dir.is_dir());
|
|
|
|
let path_str = log_dir.to_string_lossy();
|
|
assert!(path_str.contains("cli"));
|
|
assert!(path_str.contains("logs"));
|
|
|
|
// Verify we can write to the directory
|
|
let test_file = log_dir.join("test.log");
|
|
assert!(fs::write(&test_file, "test log content").is_ok());
|
|
let _ = fs::remove_file(&test_file);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_log_directory_with_date_subdir() {
|
|
// Test date-based subdirectory creation
|
|
let result = prepare_log_directory("server", true);
|
|
assert!(result.is_ok());
|
|
|
|
let log_dir = result.unwrap();
|
|
|
|
// Verify the directory was created
|
|
assert!(log_dir.exists());
|
|
assert!(log_dir.is_dir());
|
|
|
|
let path_str = log_dir.to_string_lossy();
|
|
assert!(path_str.contains("server"));
|
|
assert!(path_str.contains("logs"));
|
|
|
|
// Verify date format (YYYY-MM-DD) is present
|
|
let now = chrono::Local::now();
|
|
let date_str = now.format("%Y-%m-%d").to_string();
|
|
assert!(path_str.contains(&date_str));
|
|
|
|
// Verify path structure: logs -> component -> date
|
|
let logs_pos = path_str.find("logs").unwrap();
|
|
let component_pos = path_str.find("server").unwrap();
|
|
let date_pos = path_str.find(&date_str).unwrap();
|
|
assert!(logs_pos < component_pos);
|
|
assert!(component_pos < date_pos);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_log_directory_idempotent() {
|
|
// Test that multiple calls return the same result and don't fail
|
|
let component = "debug";
|
|
|
|
let result1 = prepare_log_directory(component, false);
|
|
assert!(result1.is_ok());
|
|
let log_dir1 = result1.unwrap();
|
|
|
|
let result2 = prepare_log_directory(component, false);
|
|
assert!(result2.is_ok());
|
|
let log_dir2 = result2.unwrap();
|
|
|
|
// Both calls should return the same path and directory should exist
|
|
assert_eq!(log_dir1, log_dir2);
|
|
assert!(log_dir1.exists());
|
|
assert!(log_dir2.exists());
|
|
|
|
// Test same behavior with date subdirectories
|
|
let result3 = prepare_log_directory(component, true);
|
|
assert!(result3.is_ok());
|
|
let log_dir3 = result3.unwrap();
|
|
|
|
let result4 = prepare_log_directory(component, true);
|
|
assert!(result4.is_ok());
|
|
let log_dir4 = result4.unwrap();
|
|
|
|
assert_eq!(log_dir3, log_dir4);
|
|
assert!(log_dir3.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_log_directory_different_components() {
|
|
// Test that different components create different directories
|
|
let components = ["cli", "server", "debug"];
|
|
let mut created_dirs = Vec::new();
|
|
|
|
for component in &components {
|
|
let result = prepare_log_directory(component, false);
|
|
assert!(result.is_ok(), "Failed for component: {}", component);
|
|
|
|
let log_dir = result.unwrap();
|
|
assert!(log_dir.exists());
|
|
assert!(log_dir.to_string_lossy().contains(component));
|
|
|
|
created_dirs.push(log_dir);
|
|
}
|
|
|
|
// Verify all directories are different
|
|
for i in 0..created_dirs.len() {
|
|
for j in i + 1..created_dirs.len() {
|
|
assert_ne!(created_dirs[i], created_dirs[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|