From 8ccbc8f1fc89f575c4afe1669a29f13552f82342 Mon Sep 17 00:00:00 2001 From: SARDONYX-sard <68905624+SARDONYX-sard@users.noreply.github.com> Date: Sat, 4 Nov 2023 07:14:02 +0900 Subject: [PATCH] feat(tauri): implement rotation gui logger --- Cargo.lock | 11 +++++ cspell.jsonc | 1 + src-tauri/Cargo.toml | 3 ++ src-tauri/src/logging.rs | 98 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6508d21..d801b75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,8 +438,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets", ] @@ -1225,13 +1227,16 @@ version = "0.1.6" dependencies = [ "anyhow", "async-trait", + "chrono", "dar2oar_core", "once_cell", "pretty_assertions", "serde", "tauri", "tauri-build", + "temp-dir", "tracing", + "tracing-appender", "tracing-subscriber", ] @@ -3430,6 +3435,12 @@ dependencies = [ "toml 0.7.8", ] +[[package]] +name = "temp-dir" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" + [[package]] name = "tempfile" version = "3.8.0" diff --git a/cspell.jsonc b/cspell.jsonc index 5345052..26a83ea 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -1,6 +1,7 @@ { "words": [ "appender", + "chrono", "Couprie", "ebnf", "fullscreen", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 67720e2..3cedf00 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-build = { version = "1.4.0", features = [] } [dependencies] async-trait = "0.1.74" anyhow = { version = "1.0.75", features = ["backtrace"] } +chrono = "0.4.31" dar2oar_core = { path = "../dar2oar_core" } once_cell = "1.18.0" serde = { version = "1.0", features = ["derive"] } # Implement (De)Serializer @@ -34,6 +35,8 @@ tracing-subscriber = "0.3.17" [dev-dependencies] pretty_assertions = "1.4.0" +temp-dir = "0.1.11" +tracing-appender = "0.2.2" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/src/logging.rs b/src-tauri/src/logging.rs index d2fc16a..0d8869a 100644 --- a/src-tauri/src/logging.rs +++ b/src-tauri/src/logging.rs @@ -1,6 +1,10 @@ use anyhow::{bail, Context as _, Result}; +use chrono::Local; use once_cell::sync::OnceCell; +use std::fs::{self, File}; +use std::path::Path; use std::str::FromStr; +use tracing::debug; use tracing_subscriber::{ filter::{self, LevelFilter}, fmt, @@ -14,11 +18,11 @@ pub static INSTANCE: OnceCell> = OnceCell::new(); pub(crate) fn init_logger(app: &tauri::App) -> Result> { let resolver = app.path_resolver(); let log_dir = &resolver.app_log_dir().context("Not found log dir")?; - let log_name = log_dir.join(format!("{}.log", app.package_info().name)); + let log_name = format!("{}.log", app.package_info().name); let fmt_layer = fmt::layer() .with_ansi(false) - .with_writer(std::fs::File::create(log_dir.join(&log_name))?); + .with_writer(create_log(log_dir, &log_name, 4)?); let (filter, reload_handle) = reload::Layer::new(filter::LevelFilter::ERROR); tracing_subscriber::registry() @@ -40,3 +44,93 @@ pub(crate) fn change_log_level(log_level: &str) -> Result<()> { None => bail!("Uninitialized logger."), } } + +/// Rotation Logger File Creator. +/// - When the maximum count is reached, delete the descending ones first and create a new log file. +fn create_log(log_dir: impl AsRef, log_name: &str, max_files: usize) -> Result { + fs::create_dir_all(&log_dir)?; + + let mut log_files = fs::read_dir(&log_dir)? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry + .file_name() + .to_str() + .map(|name| name.starts_with(log_name)) + .unwrap_or(false) + }) + .collect::>(); + + debug!("existed log files: {:?}", &log_files); + let log_file = log_dir.as_ref().join(log_name); + if log_files.len() > max_files { + log_files.sort_by(|a, b| { + // NOTE: Error in OS that can't be modified, but not considered here. + a.metadata() + .unwrap() + .modified() + .unwrap() + .cmp(&b.metadata().unwrap().modified().unwrap()) + }); + if let Some(oldest_file) = log_files.first() { + debug!("Remove old log {:?}", &oldest_file); + fs::remove_file(oldest_file.path())?; + } + } else { + let old_file = log_dir.as_ref().join(format!( + "{}_{}.log", + log_name, + Local::now().format("%F__%H_%M_%S") + )); + + if log_file.exists() { + debug!("From log_file: {:?}", &log_file); + debug!("To old_file: {:?}", &old_file); + fs::rename(&log_file, old_file)?; + } + }; + let f = File::create(log_file)?; + Ok(f) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! logger_init { + () => { + let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stdout()); + tracing_subscriber::fmt() + .with_writer(non_blocking) + .with_ansi(false) + .with_max_level(tracing::Level::DEBUG) + .init(); + }; + } + + #[test] + fn test() -> Result<()> { + logger_init!(); + let log_dir = temp_dir::TempDir::new()?; + let log_dir = log_dir.path(); + for _ in 0..5 { + create_log(log_dir, "g_dar2oar.log", 3)?; + std::thread::sleep(std::time::Duration::from_secs(1)); + } + + fn get_files_in_dir(dir_path: impl AsRef) -> Result> { + let dir = fs::read_dir(dir_path)?; + let files = dir + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().map(|ft| ft.is_file()).unwrap_or(false)) + .collect::>(); + Ok(files) + } + + let files = get_files_in_dir(log_dir)?; + debug!("{:?}", &files); + assert_eq!(files.len(), 3); + Ok(()) + } +}