From 9ef2c461bac610f985f237408a1ea5fcaf9252e3 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 13 Jun 2025 13:48:33 +0530 Subject: [PATCH] feat(agent): file-based logs with rotation (#5147) --- .../hoppscotch-agent/src-tauri/Cargo.lock | 14 ++++- .../hoppscotch-agent/src-tauri/Cargo.toml | 2 + .../hoppscotch-agent/src-tauri/src/error.rs | 8 ++- .../hoppscotch-agent/src-tauri/src/lib.rs | 52 ++---------------- .../hoppscotch-agent/src-tauri/src/logger.rs | 53 +++++++++++++++++++ .../hoppscotch-agent/src-tauri/src/main.rs | 46 +++++++++++++--- .../hoppscotch-agent/src-tauri/src/tray.rs | 7 ++- .../src-tauri/src/logger.rs | 4 +- 8 files changed, 122 insertions(+), 64 deletions(-) create mode 100644 packages/hoppscotch-agent/src-tauri/src/logger.rs diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.lock b/packages/hoppscotch-agent/src-tauri/Cargo.lock index d860708a..babc42e6 100644 --- a/packages/hoppscotch-agent/src-tauri/Cargo.lock +++ b/packages/hoppscotch-agent/src-tauri/Cargo.lock @@ -1476,6 +1476,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-rotate" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8e2fa049328a1f3295991407a88585805d126dfaadf74b9fe8c194c730aafc" +dependencies = [ + "chrono", + "flate2", +] + [[package]] name = "filetime" version = "0.2.25" @@ -2074,6 +2084,8 @@ dependencies = [ "base16", "chrono", "dashmap", + "dirs 6.0.0", + "file-rotate", "lazy_static", "native-dialog", "rand 0.8.5", @@ -6208,7 +6220,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.toml b/packages/hoppscotch-agent/src-tauri/Cargo.toml index 0c9086de..7e03afc6 100644 --- a/packages/hoppscotch-agent/src-tauri/Cargo.toml +++ b/packages/hoppscotch-agent/src-tauri/Cargo.toml @@ -45,6 +45,8 @@ tauri-plugin-single-instance = "2.0.1" tauri-plugin-http = { version = "2.0.1", features = ["gzip"] } native-dialog = "0.7.0" sha2 = "0.10.8" +file-rotate = "0.8.0" +dirs = "6.0.0" [target.'cfg(windows)'.dependencies] tempfile = { version = "3.13.0" } diff --git a/packages/hoppscotch-agent/src-tauri/src/error.rs b/packages/hoppscotch-agent/src-tauri/src/error.rs index 638a29f3..b7ef4528 100644 --- a/packages/hoppscotch-agent/src-tauri/src/error.rs +++ b/packages/hoppscotch-agent/src-tauri/src/error.rs @@ -53,11 +53,17 @@ pub enum AgentError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Log init error: {0}")] - LogInit(#[from] tracing_appender::rolling::InitError), + LogInit(String), #[error("Log init global error: {0}")] LogInitGlobal(#[from] tracing::subscriber::SetGlobalDefaultError), } +impl From for AgentError { + fn from(err: tracing_appender::rolling::InitError) -> Self { + AgentError::LogInit(err.to_string()) + } +} + impl IntoResponse for AgentError { fn into_response(self) -> Response { let (status, error_message) = match self { diff --git a/packages/hoppscotch-agent/src-tauri/src/lib.rs b/packages/hoppscotch-agent/src-tauri/src/lib.rs index 24e34209..145d39d7 100644 --- a/packages/hoppscotch-agent/src-tauri/src/lib.rs +++ b/packages/hoppscotch-agent/src-tauri/src/lib.rs @@ -3,6 +3,7 @@ pub mod controller; pub mod dialog; pub mod error; pub mod global; +pub mod logger; pub mod model; pub mod route; pub mod server; @@ -16,12 +17,13 @@ use std::sync::Arc; use tauri::{AppHandle, Emitter, Listener, Manager, WebviewWindowBuilder}; use tauri_plugin_updater::UpdaterExt; use tokio_util::sync::CancellationToken; -use tracing_subscriber::{fmt::format::JsonFields, EnvFilter}; use error::{AgentError, AgentResult}; -use model::{LogGuard, Payload}; +use model::Payload; use state::AppState; +pub const HOPPSCOTCH_AGENT_IDENTIFIER: &str = "io.hoppscotch.agent"; + #[tracing::instrument(skip(app_handle))] fn create_main_window(app_handle: &AppHandle) -> AgentResult<()> { tracing::info!("Creating main application window"); @@ -100,8 +102,6 @@ pub fn run() { })) .plugin(tauri_plugin_store::Builder::new().build()) .setup(move |app| { - // let _ = setup_logging(&app.handle())?; - tracing::info!("Setting up application"); let app_handle = app.handle(); @@ -258,47 +258,3 @@ pub fn run() { _ => {} }); } - -#[tracing::instrument(skip(app_handle))] -pub fn setup_logging(app_handle: &AppHandle) -> AgentResult<()> { - tracing::info!("Setting up logging system"); - - let app_data_dir = app_handle.path().app_data_dir()?; - tracing::debug!(path = ?app_data_dir, "Creating app data directory"); - std::fs::create_dir_all(&app_data_dir)?; - - tracing::debug!("Configuring file appender"); - let file_appender = tracing_appender::rolling::RollingFileAppender::builder() - .rotation(tracing_appender::rolling::Rotation::DAILY) - .filename_prefix("hoppscotch-agent") - .filename_suffix("log") - .max_log_files(1) - .build(&app_data_dir)?; - - let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); - - tracing::debug!("Building subscriber with JSON formatting"); - let subscriber = tracing_subscriber::fmt() - .fmt_fields(JsonFields::new()) - .with_target(false) - .with_writer(non_blocking) - .with_ansi(false) - .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339()) - .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| { - if cfg!(debug_assertions) { - "debug" - } else { - "info" - } - .into() - })) - .with_filter_reloading() - .finish(); - - tracing::subscriber::set_global_default(subscriber)?; - - app_handle.manage(LogGuard(_guard)); - - tracing::info!("Logging system initialized successfully"); - Ok(()) -} diff --git a/packages/hoppscotch-agent/src-tauri/src/logger.rs b/packages/hoppscotch-agent/src-tauri/src/logger.rs new file mode 100644 index 00000000..610b6744 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/logger.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use file_rotate::{compression::Compression, suffix::AppendCount, ContentLimit, FileRotate}; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +use crate::HOPPSCOTCH_AGENT_IDENTIFIER; + +pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard); + +pub fn setup(log_dir: &PathBuf) -> Result> { + std::fs::create_dir_all(log_dir)?; + + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into()); + + let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_AGENT_IDENTIFIER)); + tracing::info!(log_file_path =? &log_file_path); + + let file = FileRotate::new( + &log_file_path, + AppendCount::new(5), + ContentLimit::Bytes(10 * 1024 * 1024), + Compression::None, + None, + ); + + let (non_blocking, guard) = tracing_appender::non_blocking(file); + + let console_layer = fmt::layer() + .with_writer(std::io::stdout) + .with_thread_ids(true) + .with_thread_names(true) + .with_ansi(!cfg!(target_os = "windows")); + + let file_layer = fmt::layer() + .with_writer(non_blocking) + .with_ansi(false) + .with_thread_ids(true) + .with_thread_names(true) + .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339()); + + tracing_subscriber::registry() + .with(env_filter) + .with(file_layer) + .with(console_layer) + .init(); + + tracing::info!( + log_file = %log_file_path.display(), + "Logging initialized with rotating file" + ); + + Ok(LogGuard(guard)) +} diff --git a/packages/hoppscotch-agent/src-tauri/src/main.rs b/packages/hoppscotch-agent/src-tauri/src/main.rs index 8276f5b5..146be728 100644 --- a/packages/hoppscotch-agent/src-tauri/src/main.rs +++ b/packages/hoppscotch-agent/src-tauri/src/main.rs @@ -1,16 +1,46 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use hoppscotch_agent_lib::{ + logger::{self, LogGuard}, + HOPPSCOTCH_AGENT_IDENTIFIER, +}; fn main() { - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()), - ) - .with(tracing_subscriber::fmt::layer().without_time()) - .init(); + // Follows how `tauri` does this and exactly matches desktop's approach + // see: https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/path/desktop.rs + let path = { + #[cfg(target_os = "macos")] + let path = + dirs::home_dir().map(|dir| dir.join("Library/Logs").join(HOPPSCOTCH_AGENT_IDENTIFIER)); + + #[cfg(not(target_os = "macos"))] + let path = + dirs::data_local_dir().map(|dir| dir.join(HOPPSCOTCH_AGENT_IDENTIFIER).join("logs")); + + path + }; + + let Some(log_file_path) = path else { + eprint!("Failed to setup logging!"); + + println!("Starting Hoppscotch Agent..."); + + return hoppscotch_agent_lib::run(); + }; + + let Ok(LogGuard(guard)) = logger::setup(&log_file_path) else { + eprint!("Failed to setup logging!"); + + println!("Starting Hoppscotch Agent..."); + + return hoppscotch_agent_lib::run(); + }; + + // This keeps the guard alive, this is scoped to `main` + // so it can only drop when the entire app exits, + // so safe to have it like this. + let _guard = guard; tracing::info!("Starting Hoppscotch Agent..."); diff --git a/packages/hoppscotch-agent/src-tauri/src/tray.rs b/packages/hoppscotch-agent/src-tauri/src/tray.rs index 94952991..5ae79009 100644 --- a/packages/hoppscotch-agent/src-tauri/src/tray.rs +++ b/packages/hoppscotch-agent/src-tauri/src/tray.rs @@ -96,10 +96,9 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> { } } "maximize_window" => { - app.emit("maximize-window", ()) - .unwrap_or_else(|e| { - tracing::error!("Failed to emit maximize-window event: {}", e); - }); + app.emit("maximize-window", ()).unwrap_or_else(|e| { + tracing::error!("Failed to emit maximize-window event: {}", e); + }); if let Err(e) = show_main_window(&app) { tracing::error!("Failed to maximize window: {}", e); } diff --git a/packages/hoppscotch-desktop/src-tauri/src/logger.rs b/packages/hoppscotch-desktop/src-tauri/src/logger.rs index ea1a5813..7455b03f 100644 --- a/packages/hoppscotch-desktop/src-tauri/src/logger.rs +++ b/packages/hoppscotch-desktop/src-tauri/src/logger.rs @@ -10,7 +10,7 @@ pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard); pub fn setup(log_dir: &PathBuf) -> Result> { std::fs::create_dir_all(log_dir)?; - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| format!("debug").into()); + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into()); let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_DESKTOP_IDENTIFIER)); tracing::info!(log_file_path =? &log_file_path); @@ -45,7 +45,7 @@ pub fn setup(log_dir: &PathBuf) -> Result> tracing::info!( log_file = %log_file_path.display(), - "Logging initialized with single file" + "Logging initialized with rotating file" ); Ok(LogGuard(guard))