mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Add time crate for session log filename generation and refactor logging setup
- Introduced the `time` crate to generate session log filenames in UTC format, enhancing log file naming consistency. - Refactored the logging setup to create log files based on the generated filenames, improving the clarity of log management. - Updated the `compress_old_logs` function to streamline log file handling, ensuring proper cleanup of old logs. These changes aim to improve the logging system's functionality and maintainability within the application.
This commit is contained in:
parent
491506fafd
commit
1f7682dfac
3 changed files with 113 additions and 72 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -251,6 +251,7 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"time 0.3.47",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ lexical-write-float = "1.0.6"
|
||||||
strum = { version = "0.27", features = ["derive"] }
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
toml = "0.9"
|
toml = "0.9"
|
||||||
termcolor = "1.4.1"
|
termcolor = "1.4.1"
|
||||||
|
time = "0.3"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
tokio = { version = "1.49.0", features = ["rt", "rt-multi-thread", "macros", "sync"] }
|
tokio = { version = "1.49.0", features = ["rt", "rt-multi-thread", "macros", "sync"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
|
||||||
183
src/logging.rs
183
src/logging.rs
|
|
@ -16,6 +16,22 @@ pub struct LogGuard {
|
||||||
_file_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
|
_file_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a session log filename like `atlas-serve.2026-03-01_14-32-05.log`.
|
||||||
|
/// Uses UTC time, hyphens and underscores only (safe on all OSes).
|
||||||
|
fn session_log_filename(command_name: &str) -> String {
|
||||||
|
let now = time::OffsetDateTime::now_utc();
|
||||||
|
format!(
|
||||||
|
"{}.{:04}-{:02}-{:02}_{:02}-{:02}-{:02}.log",
|
||||||
|
command_name,
|
||||||
|
now.year(),
|
||||||
|
now.month() as u8,
|
||||||
|
now.day(),
|
||||||
|
now.hour(),
|
||||||
|
now.minute(),
|
||||||
|
now.second(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init_logging(
|
pub fn init_logging(
|
||||||
verbosity: u8,
|
verbosity: u8,
|
||||||
color: ColorChoice,
|
color: ColorChoice,
|
||||||
|
|
@ -55,30 +71,41 @@ pub fn init_logging(
|
||||||
|
|
||||||
match std::fs::create_dir_all(&log_dir) {
|
match std::fs::create_dir_all(&log_dir) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
compress_old_logs(&log_dir, command_name);
|
compress_old_logs(&log_dir);
|
||||||
|
|
||||||
let file_appender = tracing_appender::rolling::Builder::new()
|
let log_filename = session_log_filename(command_name);
|
||||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
let log_path = log_dir.join(&log_filename);
|
||||||
.filename_prefix(command_name)
|
|
||||||
.filename_suffix("log")
|
|
||||||
.build(&log_dir)
|
|
||||||
.expect("Failed to create rolling file appender");
|
|
||||||
|
|
||||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
match std::fs::OpenOptions::new()
|
||||||
file_guard = Some(guard);
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&log_path)
|
||||||
|
{
|
||||||
|
Ok(file) => {
|
||||||
|
let (non_blocking, guard) = tracing_appender::non_blocking(file);
|
||||||
|
file_guard = Some(guard);
|
||||||
|
|
||||||
let file_filter = EnvFilter::new(level.to_string());
|
let file_filter = EnvFilter::new(level.to_string());
|
||||||
|
|
||||||
let layer = fmt::layer()
|
let layer = fmt::layer()
|
||||||
.with_writer(non_blocking)
|
.with_writer(non_blocking)
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.with_timer(UtcTime::rfc_3339())
|
.with_timer(UtcTime::rfc_3339())
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.with_thread_names(true)
|
.with_thread_names(true)
|
||||||
.with_level(true)
|
.with_level(true)
|
||||||
.with_filter(file_filter);
|
.with_filter(file_filter);
|
||||||
|
|
||||||
Some(layer)
|
Some(layer)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: could not create log file {}: {e}",
|
||||||
|
log_path.display()
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|
@ -102,15 +129,12 @@ pub fn init_logging(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compress_old_logs(log_dir: &Path, command_name: &str) {
|
fn compress_old_logs(log_dir: &Path) {
|
||||||
let today = {
|
let now_secs = std::time::SystemTime::now()
|
||||||
let now = std::time::SystemTime::now();
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
let since_epoch = now
|
.unwrap_or_default()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.as_secs();
|
||||||
.unwrap_or_default();
|
let today = now_secs / 86400;
|
||||||
let days = since_epoch.as_secs() / 86400;
|
|
||||||
days
|
|
||||||
};
|
|
||||||
|
|
||||||
let entries = match std::fs::read_dir(log_dir) {
|
let entries = match std::fs::read_dir(log_dir) {
|
||||||
Ok(entries) => entries,
|
Ok(entries) => entries,
|
||||||
|
|
@ -143,22 +167,12 @@ fn compress_old_logs(log_dir: &Path, command_name: &str) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file_name.starts_with(command_name) {
|
// Try to open for exclusive write to check if another process holds it.
|
||||||
|
// On Windows this fails if another Atlas process has the file open.
|
||||||
|
if std::fs::OpenOptions::new().write(true).open(&path).is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(meta) = entry.metadata() {
|
|
||||||
if let Ok(modified) = meta.modified() {
|
|
||||||
let file_days = modified
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.map(|d| d.as_secs() / 86400)
|
|
||||||
.unwrap_or(today);
|
|
||||||
if file_days >= today {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let gz_path = path.with_extension("log.gz");
|
let gz_path = path.with_extension("log.gz");
|
||||||
if let Ok(input) = std::fs::read(&path) {
|
if let Ok(input) = std::fs::read(&path) {
|
||||||
let gz_file = match std::fs::File::create(&gz_path) {
|
let gz_file = match std::fs::File::create(&gz_path) {
|
||||||
|
|
@ -458,62 +472,87 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compress_old_logs_compresses_old_files() {
|
fn compress_old_logs_compresses_all_log_files() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let log_dir = dir.path();
|
let log_dir = dir.path();
|
||||||
|
|
||||||
let old_file = log_dir.join("atlas-serve.2020-01-01.log");
|
let serve_file = log_dir.join("atlas-serve.2020-01-01_10-00-00.log");
|
||||||
std::fs::write(&old_file, "old log content").unwrap();
|
std::fs::write(&serve_file, "serve log content").unwrap();
|
||||||
|
|
||||||
let mtime =
|
let build_file = log_dir.join("atlas-build.2020-01-01_11-00-00.log");
|
||||||
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86400 * 18262);
|
std::fs::write(&build_file, "build log content").unwrap();
|
||||||
filetime::set_file_mtime(&old_file, filetime::FileTime::from_system_time(mtime))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
compress_old_logs(log_dir, "atlas-serve");
|
compress_old_logs(log_dir);
|
||||||
|
|
||||||
assert!(!old_file.exists(), "original .log file should be deleted");
|
assert!(!serve_file.exists(), "serve .log should be deleted");
|
||||||
let gz_file = log_dir.join("atlas-serve.2020-01-01.log.gz");
|
assert!(!build_file.exists(), "build .log should be deleted");
|
||||||
assert!(gz_file.exists(), ".log.gz file should be created");
|
|
||||||
|
|
||||||
let gz_data = std::fs::read(&gz_file).unwrap();
|
let serve_gz = log_dir.join("atlas-serve.2020-01-01_10-00-00.log.gz");
|
||||||
|
assert!(serve_gz.exists(), "serve .log.gz should be created");
|
||||||
|
|
||||||
|
let build_gz = log_dir.join("atlas-build.2020-01-01_11-00-00.log.gz");
|
||||||
|
assert!(build_gz.exists(), "build .log.gz should be created");
|
||||||
|
|
||||||
|
let gz_data = std::fs::read(&serve_gz).unwrap();
|
||||||
let mut decoder = flate2::read::GzDecoder::new(&gz_data[..]);
|
let mut decoder = flate2::read::GzDecoder::new(&gz_data[..]);
|
||||||
let mut decompressed = String::new();
|
let mut decompressed = String::new();
|
||||||
decoder.read_to_string(&mut decompressed).unwrap();
|
decoder.read_to_string(&mut decompressed).unwrap();
|
||||||
assert_eq!(decompressed, "old log content");
|
assert_eq!(decompressed, "serve log content");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compress_old_logs_skips_other_commands() {
|
fn compress_old_logs_skips_non_log_files() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let log_dir = dir.path();
|
let log_dir = dir.path();
|
||||||
|
|
||||||
let other_file = log_dir.join("atlas-build.2020-01-01.log");
|
let txt_file = log_dir.join("notes.txt");
|
||||||
std::fs::write(&other_file, "build log").unwrap();
|
std::fs::write(&txt_file, "not a log").unwrap();
|
||||||
|
|
||||||
|
compress_old_logs(log_dir);
|
||||||
|
|
||||||
|
assert!(txt_file.exists(), "non-.log files should be left alone");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compress_old_logs_prunes_old_gz() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let log_dir = dir.path();
|
||||||
|
|
||||||
|
let old_gz = log_dir.join("atlas-serve.2020-01-01_10-00-00.log.gz");
|
||||||
|
std::fs::write(&old_gz, "old compressed").unwrap();
|
||||||
|
|
||||||
let mtime =
|
let mtime =
|
||||||
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86400 * 18262);
|
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86400 * 18262);
|
||||||
filetime::set_file_mtime(&other_file, filetime::FileTime::from_system_time(mtime))
|
filetime::set_file_mtime(&old_gz, filetime::FileTime::from_system_time(mtime))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
compress_old_logs(log_dir, "atlas-serve");
|
compress_old_logs(log_dir);
|
||||||
|
|
||||||
assert!(
|
assert!(!old_gz.exists(), "old .log.gz (>7 days) should be pruned");
|
||||||
other_file.exists(),
|
|
||||||
"other command's log should NOT be compressed"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compress_old_logs_skips_today() {
|
fn session_log_filename_format() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let name = session_log_filename("atlas-serve");
|
||||||
let log_dir = dir.path();
|
assert!(
|
||||||
|
name.starts_with("atlas-serve."),
|
||||||
|
"should start with command prefix"
|
||||||
|
);
|
||||||
|
assert!(name.ends_with(".log"), "should end with .log");
|
||||||
|
assert!(!name.contains(':'), "no colons (Windows-safe)");
|
||||||
|
assert!(!name.contains('/'), "no slashes");
|
||||||
|
assert!(!name.contains('\\'), "no backslashes");
|
||||||
|
|
||||||
let today_file = log_dir.join("atlas-serve.today.log");
|
let middle = &name["atlas-serve.".len()..name.len() - ".log".len()];
|
||||||
std::fs::write(&today_file, "today's log").unwrap();
|
assert_eq!(
|
||||||
|
middle.len(),
|
||||||
compress_old_logs(log_dir, "atlas-serve");
|
19,
|
||||||
|
"timestamp should be YYYY-MM-DD_HH-MM-SS (19 chars)"
|
||||||
assert!(today_file.exists(), "today's log should NOT be compressed");
|
);
|
||||||
|
assert_eq!(&middle[4..5], "-");
|
||||||
|
assert_eq!(&middle[7..8], "-");
|
||||||
|
assert_eq!(&middle[10..11], "_");
|
||||||
|
assert_eq!(&middle[13..14], "-");
|
||||||
|
assert_eq!(&middle[16..17], "-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue