Skip to content

Commit 9ef2c46

Browse files
feat(agent): file-based logs with rotation (hoppscotch#5147)
1 parent 4302a45 commit 9ef2c46

File tree

8 files changed

+122
-64
lines changed

8 files changed

+122
-64
lines changed

packages/hoppscotch-agent/src-tauri/Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/hoppscotch-agent/src-tauri/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ tauri-plugin-single-instance = "2.0.1"
4545
tauri-plugin-http = { version = "2.0.1", features = ["gzip"] }
4646
native-dialog = "0.7.0"
4747
sha2 = "0.10.8"
48+
file-rotate = "0.8.0"
49+
dirs = "6.0.0"
4850

4951
[target.'cfg(windows)'.dependencies]
5052
tempfile = { version = "3.13.0" }

packages/hoppscotch-agent/src-tauri/src/error.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,17 @@ pub enum AgentError {
5353
#[error("IO error: {0}")]
5454
Io(#[from] std::io::Error),
5555
#[error("Log init error: {0}")]
56-
LogInit(#[from] tracing_appender::rolling::InitError),
56+
LogInit(String),
5757
#[error("Log init global error: {0}")]
5858
LogInitGlobal(#[from] tracing::subscriber::SetGlobalDefaultError),
5959
}
6060

61+
impl From<tracing_appender::rolling::InitError> for AgentError {
62+
fn from(err: tracing_appender::rolling::InitError) -> Self {
63+
AgentError::LogInit(err.to_string())
64+
}
65+
}
66+
6167
impl IntoResponse for AgentError {
6268
fn into_response(self) -> Response {
6369
let (status, error_message) = match self {

packages/hoppscotch-agent/src-tauri/src/lib.rs

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod controller;
33
pub mod dialog;
44
pub mod error;
55
pub mod global;
6+
pub mod logger;
67
pub mod model;
78
pub mod route;
89
pub mod server;
@@ -16,12 +17,13 @@ use std::sync::Arc;
1617
use tauri::{AppHandle, Emitter, Listener, Manager, WebviewWindowBuilder};
1718
use tauri_plugin_updater::UpdaterExt;
1819
use tokio_util::sync::CancellationToken;
19-
use tracing_subscriber::{fmt::format::JsonFields, EnvFilter};
2020

2121
use error::{AgentError, AgentResult};
22-
use model::{LogGuard, Payload};
22+
use model::Payload;
2323
use state::AppState;
2424

25+
pub const HOPPSCOTCH_AGENT_IDENTIFIER: &str = "io.hoppscotch.agent";
26+
2527
#[tracing::instrument(skip(app_handle))]
2628
fn create_main_window(app_handle: &AppHandle) -> AgentResult<()> {
2729
tracing::info!("Creating main application window");
@@ -100,8 +102,6 @@ pub fn run() {
100102
}))
101103
.plugin(tauri_plugin_store::Builder::new().build())
102104
.setup(move |app| {
103-
// let _ = setup_logging(&app.handle())?;
104-
105105
tracing::info!("Setting up application");
106106
let app_handle = app.handle();
107107

@@ -258,47 +258,3 @@ pub fn run() {
258258
_ => {}
259259
});
260260
}
261-
262-
#[tracing::instrument(skip(app_handle))]
263-
pub fn setup_logging(app_handle: &AppHandle) -> AgentResult<()> {
264-
tracing::info!("Setting up logging system");
265-
266-
let app_data_dir = app_handle.path().app_data_dir()?;
267-
tracing::debug!(path = ?app_data_dir, "Creating app data directory");
268-
std::fs::create_dir_all(&app_data_dir)?;
269-
270-
tracing::debug!("Configuring file appender");
271-
let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
272-
.rotation(tracing_appender::rolling::Rotation::DAILY)
273-
.filename_prefix("hoppscotch-agent")
274-
.filename_suffix("log")
275-
.max_log_files(1)
276-
.build(&app_data_dir)?;
277-
278-
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
279-
280-
tracing::debug!("Building subscriber with JSON formatting");
281-
let subscriber = tracing_subscriber::fmt()
282-
.fmt_fields(JsonFields::new())
283-
.with_target(false)
284-
.with_writer(non_blocking)
285-
.with_ansi(false)
286-
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
287-
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| {
288-
if cfg!(debug_assertions) {
289-
"debug"
290-
} else {
291-
"info"
292-
}
293-
.into()
294-
}))
295-
.with_filter_reloading()
296-
.finish();
297-
298-
tracing::subscriber::set_global_default(subscriber)?;
299-
300-
app_handle.manage(LogGuard(_guard));
301-
302-
tracing::info!("Logging system initialized successfully");
303-
Ok(())
304-
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::path::PathBuf;
2+
3+
use file_rotate::{compression::Compression, suffix::AppendCount, ContentLimit, FileRotate};
4+
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
5+
6+
use crate::HOPPSCOTCH_AGENT_IDENTIFIER;
7+
8+
pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
9+
10+
pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
11+
std::fs::create_dir_all(log_dir)?;
12+
13+
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into());
14+
15+
let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_AGENT_IDENTIFIER));
16+
tracing::info!(log_file_path =? &log_file_path);
17+
18+
let file = FileRotate::new(
19+
&log_file_path,
20+
AppendCount::new(5),
21+
ContentLimit::Bytes(10 * 1024 * 1024),
22+
Compression::None,
23+
None,
24+
);
25+
26+
let (non_blocking, guard) = tracing_appender::non_blocking(file);
27+
28+
let console_layer = fmt::layer()
29+
.with_writer(std::io::stdout)
30+
.with_thread_ids(true)
31+
.with_thread_names(true)
32+
.with_ansi(!cfg!(target_os = "windows"));
33+
34+
let file_layer = fmt::layer()
35+
.with_writer(non_blocking)
36+
.with_ansi(false)
37+
.with_thread_ids(true)
38+
.with_thread_names(true)
39+
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339());
40+
41+
tracing_subscriber::registry()
42+
.with(env_filter)
43+
.with(file_layer)
44+
.with(console_layer)
45+
.init();
46+
47+
tracing::info!(
48+
log_file = %log_file_path.display(),
49+
"Logging initialized with rotating file"
50+
);
51+
52+
Ok(LogGuard(guard))
53+
}

packages/hoppscotch-agent/src-tauri/src/main.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
11
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

4-
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
4+
use hoppscotch_agent_lib::{
5+
logger::{self, LogGuard},
6+
HOPPSCOTCH_AGENT_IDENTIFIER,
7+
};
58

69
fn main() {
7-
tracing_subscriber::registry()
8-
.with(
9-
tracing_subscriber::EnvFilter::try_from_default_env()
10-
.unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()),
11-
)
12-
.with(tracing_subscriber::fmt::layer().without_time())
13-
.init();
10+
// Follows how `tauri` does this and exactly matches desktop's approach
11+
// see: https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/path/desktop.rs
12+
let path = {
13+
#[cfg(target_os = "macos")]
14+
let path =
15+
dirs::home_dir().map(|dir| dir.join("Library/Logs").join(HOPPSCOTCH_AGENT_IDENTIFIER));
16+
17+
#[cfg(not(target_os = "macos"))]
18+
let path =
19+
dirs::data_local_dir().map(|dir| dir.join(HOPPSCOTCH_AGENT_IDENTIFIER).join("logs"));
20+
21+
path
22+
};
23+
24+
let Some(log_file_path) = path else {
25+
eprint!("Failed to setup logging!");
26+
27+
println!("Starting Hoppscotch Agent...");
28+
29+
return hoppscotch_agent_lib::run();
30+
};
31+
32+
let Ok(LogGuard(guard)) = logger::setup(&log_file_path) else {
33+
eprint!("Failed to setup logging!");
34+
35+
println!("Starting Hoppscotch Agent...");
36+
37+
return hoppscotch_agent_lib::run();
38+
};
39+
40+
// This keeps the guard alive, this is scoped to `main`
41+
// so it can only drop when the entire app exits,
42+
// so safe to have it like this.
43+
let _guard = guard;
1444

1545
tracing::info!("Starting Hoppscotch Agent...");
1646

packages/hoppscotch-agent/src-tauri/src/tray.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,9 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
9696
}
9797
}
9898
"maximize_window" => {
99-
app.emit("maximize-window", ())
100-
.unwrap_or_else(|e| {
101-
tracing::error!("Failed to emit maximize-window event: {}", e);
102-
});
99+
app.emit("maximize-window", ()).unwrap_or_else(|e| {
100+
tracing::error!("Failed to emit maximize-window event: {}", e);
101+
});
103102
if let Err(e) = show_main_window(&app) {
104103
tracing::error!("Failed to maximize window: {}", e);
105104
}

packages/hoppscotch-desktop/src-tauri/src/logger.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
1010
pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
1111
std::fs::create_dir_all(log_dir)?;
1212

13-
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| format!("debug").into());
13+
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into());
1414

1515
let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_DESKTOP_IDENTIFIER));
1616
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>>
4545

4646
tracing::info!(
4747
log_file = %log_file_path.display(),
48-
"Logging initialized with single file"
48+
"Logging initialized with rotating file"
4949
);
5050

5151
Ok(LogGuard(guard))

0 commit comments

Comments
 (0)