feat(agent): file-based logs with rotation (#5147)

This commit is contained in:
Shreyas 2025-06-13 13:48:33 +05:30 committed by GitHub
parent 4302a45477
commit 9ef2c461ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 122 additions and 64 deletions

View file

@ -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]]

View file

@ -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" }

View file

@ -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<tracing_appender::rolling::InitError> 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 {

View file

@ -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(())
}

View file

@ -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<LogGuard, Box<dyn std::error::Error>> {
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))
}

View file

@ -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...");

View file

@ -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);
}

View file

@ -10,7 +10,7 @@ pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
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<LogGuard, Box<dyn std::error::Error>>
tracing::info!(
log_file = %log_file_path.display(),
"Logging initialized with single file"
"Logging initialized with rotating file"
);
Ok(LogGuard(guard))