feat(desktop): file-based logs with rotation (#5009)

This PR adds a file-based logging system with size-based rotation to the desktop application. It essentially redirects existing diagnostic to size-based rotating files for troubleshooting environment-specific issues.

Closes HFE-801

The desktop application currently lacks a persistent logging mechanism in production environments. Logs are only available through the development mode console.

This PR will help diagnose issues reported in #4859, #4950, #5003, discussions #4984 and #4986.

Mainly aiming to understand errors in specific environments that can't be reproduced in our testing setups.

This implementation uses the tracing ecosystem (`tracing`, `tracing_subscriber`, `tracing_appender`) along with `file_rotate` to create log files in the platform's log directory. The logs are automatically rotated when they reach `10MB`, with a maximum of `5` files retained.

Thinking 10 * 5 MB is reasonable disk usage while maintaining sufficient history.

The system currently writes to both the console (with ANSI colors where supported) and to files (without ANSI formatting for readability). Log levels are currently controlled via the `RUST_LOG` environment variable, defaulting to "debug" when not specified.

| OS      | Log File Path                                        |
|---------|------------------------------------------------------|
| Windows | `C:\Users\<username>\AppData\Local\io.hoppscotch.desktop\logs\io.hoppscotch.desktop.log` |
| macOS   | `~/Library/Logs/io.hoppscotch.desktop/io.hoppscotch.desktop.log` |
| Linux   | `~/.local/share/io.hoppscotch.desktop/logs/io.hoppscotch.desktop.log` |
This commit is contained in:
Shreyas 2025-04-25 17:09:51 +05:30 committed by GitHub
parent 13b46d52f2
commit fbeb0e56be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 111 additions and 105 deletions

View file

@ -1444,6 +1444,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"
@ -2026,6 +2036,7 @@ name = "hoppscotch-desktop"
version = "25.3.2"
dependencies = [
"axum",
"file-rotate",
"portpicker",
"serde",
"serde_json",
@ -2044,6 +2055,7 @@ dependencies = [
"tokio",
"tower-http",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
@ -2619,9 +2631,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.172"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libloading"
@ -2928,15 +2940,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -3063,15 +3066,6 @@ dependencies = [
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "objc2-core-image"
version = "0.2.2"
@ -4747,16 +4741,13 @@ dependencies = [
]
[[package]]
name = "sysinfo"
version = "0.34.2"
name = "sys-info"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c"
dependencies = [
"cc",
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"windows 0.57.0",
]
[[package]]
@ -4993,7 +4984,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-appload"
version = "0.1.0"
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-appload#60adb82d0cc886004307057194cf680373e14e02"
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-appload#1c2e8b19db7f1b6af6d00abb907f15cdc2017298"
dependencies = [
"base64 0.22.1",
"blake3",
@ -5016,7 +5007,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"sysinfo",
"sys-info",
"tauri",
"tauri-plugin",
"thiserror 2.0.7",
@ -5045,7 +5036,7 @@ dependencies = [
"tracing",
"url",
"windows-registry 0.3.0",
"windows-result 0.2.0",
"windows-result",
]
[[package]]
@ -5606,6 +5597,18 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror 1.0.69",
"time",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
@ -6148,8 +6151,8 @@ dependencies = [
"webview2-com-sys",
"windows 0.58.0",
"windows-core 0.58.0",
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-implement",
"windows-interface",
]
[[package]]
@ -6228,16 +6231,6 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
dependencies = [
"windows-core 0.57.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.58.0"
@ -6257,42 +6250,19 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
dependencies = [
"windows-implement 0.57.0",
"windows-interface 0.57.0",
"windows-result 0.1.2",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-implement",
"windows-interface",
"windows-result",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-implement"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
@ -6304,17 +6274,6 @@ dependencies = [
"syn 2.0.90",
]
[[package]]
name = "windows-interface"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
@ -6332,7 +6291,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result 0.2.0",
"windows-result",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
@ -6343,20 +6302,11 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a"
dependencies = [
"windows-result 0.2.0",
"windows-result",
"windows-strings 0.2.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
@ -6372,7 +6322,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"windows-result",
"windows-targets 0.52.6",
]

View file

@ -24,6 +24,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = [] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-appender = { version = "0.2.3" }
tauri-plugin-store = "2.2.0"
tauri-plugin-dialog = "2.2.0"
tauri-plugin-fs = "2.2.0"
@ -35,6 +36,7 @@ tower-http = { version = "0.6.2", features = ["cors"] }
portpicker = "0.1.1"
tokio = "1.43.0"
tauri-plugin-process = "2.2.0"
file-rotate = "0.8.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2.3.1"

View file

@ -1,3 +1,4 @@
pub mod logger;
pub mod server;
pub mod updater;
@ -19,6 +20,12 @@ fn hopp_auth_port() -> u16 {
pub fn run() {
tracing::info!("Starting Hoppscotch Desktop v{}", env!("CARGO_PKG_VERSION"));
let server_port = portpicker::pick_unused_port().expect("Cannot find unused port");
SERVER_PORT
.set(server_port)
.expect("Failed to set server port");
tracing::info!("Selected server port: {}", server_port);
tauri::Builder::default()
.plugin(
tauri_plugin_window_state::Builder::new()
@ -61,14 +68,20 @@ pub fn run() {
})
.setup(|app| {
let handle = app.handle().clone();
let port = portpicker::pick_unused_port().expect("Cannot find unused port");
SERVER_PORT.set(port).expect("Failed to set server port");
let port = *SERVER_PORT.get().expect("Server port not initialized");
tracing::info!(port = port, "Initializing server with pre-selected port");
let port = server::init(port, handle);
tracing::info!(port = port, "Server initialization complete");
Ok(())
})
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_fs::init())
.setup(|app| {
logger::setup(app.handle().clone())?;
tracing::info!("Logger setup complete");
Ok(())
})
.plugin(tauri_plugin_appload::init(
VendorConfigBuilder::new().bundle(
include_bytes!("../../bundle.zip").to_vec(),

View file

@ -0,0 +1,52 @@
use file_rotate::{compression::Compression, suffix::AppendCount, ContentLimit, FileRotate};
use tauri::{AppHandle, Manager, Runtime};
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
pub fn setup<R: Runtime>(app_handle: AppHandle<R>) -> Result<(), Box<dyn std::error::Error>> {
let log_dir = app_handle.path().app_log_dir()?;
std::fs::create_dir_all(&log_dir)?;
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| format!("debug").into());
let log_file_path = log_dir.join("io.hoppscotch.desktop.log");
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);
tracing_subscriber::registry()
.with(env_filter)
.with(file_layer)
.with(console_layer)
.init();
app_handle.manage(LogGuard(guard));
tracing::info!(
log_file = %log_file_path.display(),
"Logging initialized with single file"
);
Ok(())
}

View file

@ -1,18 +1,7 @@
// 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};
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();
tracing::info!("Starting Hoppscotch Desktop...");
println!("Starting Hoppscotch Desktop application...");
hoppscotch_desktop_lib::run()
}