[PR #5044] [MERGED] feat(desktop): complete lifecycle logs #5026

Closed
opened 2026-03-17 02:30:50 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/hoppscotch/hoppscotch/pull/5044
Author: @CuriousCorrelation
Created: 5/2/2025
Status: Merged
Merged: 5/21/2025
Merged by: @AndrewBastin

Base: nextHead: feat-desktop-complete-logs


📝 Commits (1)

  • a0f112f feat(desktop): complete lifecycle logs

📊 Changes

5 files changed (+91 additions, -18 deletions)

View changed files

📝 packages/hoppscotch-desktop/src-tauri/Cargo.lock (+39 -6)
📝 packages/hoppscotch-desktop/src-tauri/Cargo.toml (+1 -0)
📝 packages/hoppscotch-desktop/src-tauri/src/lib.rs (+1 -3)
📝 packages/hoppscotch-desktop/src-tauri/src/logger.rs (+8 -8)
📝 packages/hoppscotch-desktop/src-tauri/src/main.rs (+42 -1)

📄 Description

This changes how logging is initialized and managed, fixing an issue where some logs were missing from io.hoppscotch.log files.

Closes HFE-838

Basically some debug log messages just weren't appearing in the logs. This was quite odd because other debug messages from the same module were appearing correctly.

In Tauri app, logging is tricky... mainly because of the application lifecycle. The application starts in main.rs before any Tauri-specific functionality is available. Having log sinks setup at main would be great, but we also need file-based logging that requires the Tauri AppHandle to determine the right log file paths.

There is a "theoretical" way to achieve this, to simply initialize logging twice:

// In main.rs - Basic console logging
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();

// Later in lib.rs - File-based logging
logger::setup(app.handle().clone())?;

Why this doesn't work is tracing needs a global dispatcher

thread 'main' panicked at .../tracing-subscriber-0.3.19/src/util.rs:91:14:
failed to set global default subscriber: SetGlobalDefaultError("a global
default trace dispatcher has already been set")

This error occurs because the tracing ecosystem only allows setting the global default subscriber once, see:
https://docs.rs/tracing-subscriber/latest/src/tracing_subscriber/util.rs.html#63-92

The issue is that when we use init() to initialize the global dispatcher in main.rs, we can't set it again in lib.rs. This means any early logs in the application lifecycle before the Tauri AppHandle is available were getting captured by the basic console logger, but we couldn't add file-based logging later.

The missing logs showed up when running the application with only the basic console logger set in main.rs, but this approach lacks the file persistence based logging. Essentially we can see the disk space check and other initialization logs in the console, but they weren't being saved to the log file.

DEBUG tauri_plugin_appload::storage::manager: Checking available disk space required_space=15972964
DEBUG tauri_plugin_appload::storage::manager: Sufficient disk space available available_space=649181622

A classic timing issue: we need file-based logging set up before Tauri plugins begin initialization but we could only get the appropriate paths after Tauri starts.

The solution is - rather than relying on Tauri's built-in path resolution which are only available after the app is created, we implemented a similar path resolution directly in main.rs:

// Follows how `tauri` does this
// 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_DESKTOP_IDENTIFIER));

    #[cfg(not(target_os = "macos"))]
    let path =
        dirs::data_local_dir().map(|dir| dir.join(HOPPSCOTCH_DESKTOP_IDENTIFIER).join("logs"));

    path
};

This mimics Tauri's own path resolution logic to find the log directory before the Tauri application starts.

Next, change logger.rs to accept a path rather than an AppHandle:

pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
    std::fs::create_dir_all(log_dir)?;
    // ... logging setup ...
    Ok(LogGuard(guard))
}

So now, instead of managing the log guard within the setup function using app_handle.manage(), we return the guard to the caller, allowing it to be managed in the main function's scope:

let Ok(LogGuard(guard)) = logger::setup(&log_file_path) else {
    eprint!("Failed to setup logging!");
    println!("Starting Hoppscotch Desktop...");
    return hoppscotch_desktop_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;

The _guard is important. Underscore is just for warnings, but the variable itself keeps the guard alive for the lifetime of the main. Since the guard is responsible for buffered logs to get flushed to disk.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/hoppscotch/hoppscotch/pull/5044 **Author:** [@CuriousCorrelation](https://github.com/CuriousCorrelation) **Created:** 5/2/2025 **Status:** ✅ Merged **Merged:** 5/21/2025 **Merged by:** [@AndrewBastin](https://github.com/AndrewBastin) **Base:** `next` ← **Head:** `feat-desktop-complete-logs` --- ### 📝 Commits (1) - [`a0f112f`](https://github.com/hoppscotch/hoppscotch/commit/a0f112f8ce2c83657bc078e7e2aad1ba4e125535) feat(desktop): complete lifecycle logs ### 📊 Changes **5 files changed** (+91 additions, -18 deletions) <details> <summary>View changed files</summary> 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.lock` (+39 -6) 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.toml` (+1 -0) 📝 `packages/hoppscotch-desktop/src-tauri/src/lib.rs` (+1 -3) 📝 `packages/hoppscotch-desktop/src-tauri/src/logger.rs` (+8 -8) 📝 `packages/hoppscotch-desktop/src-tauri/src/main.rs` (+42 -1) </details> ### 📄 Description This changes how logging is initialized and managed, fixing an issue where some logs were missing from `io.hoppscotch.log` files. Closes HFE-838 Basically some debug log messages just weren't appearing in the logs. This was quite odd because other debug messages from the same module were appearing correctly. In Tauri app, logging is tricky... mainly because of the application lifecycle. The application starts in `main.rs` before any Tauri-specific functionality is available. Having log sinks setup at `main` would be great, but we also need file-based logging that requires the Tauri AppHandle to determine the right log file paths. There is a "theoretical" way to achieve this, to simply initialize logging twice: ```rust // In main.rs - Basic console logging 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(); // Later in lib.rs - File-based logging logger::setup(app.handle().clone())?; ``` Why this doesn't work is `tracing` needs a global dispatcher ``` thread 'main' panicked at .../tracing-subscriber-0.3.19/src/util.rs:91:14: failed to set global default subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set") ``` This error occurs because the `tracing` ecosystem only allows setting the global default subscriber once, see: https://docs.rs/tracing-subscriber/latest/src/tracing_subscriber/util.rs.html#63-92 The issue is that when we use `init()` to initialize the global dispatcher in `main.rs`, we can't set it again in `lib.rs`. This means any early logs in the application lifecycle before the Tauri `AppHandle` is available were getting captured by the basic console logger, but we couldn't add file-based logging later. The missing logs showed up when running the application with only the basic console logger set in `main.rs`, but this approach lacks the file persistence based logging. Essentially we can see the disk space check and other initialization logs in the console, but they weren't being saved to the log file. ```rust DEBUG tauri_plugin_appload::storage::manager: Checking available disk space required_space=15972964 DEBUG tauri_plugin_appload::storage::manager: Sufficient disk space available available_space=649181622 ``` A classic timing issue: we need file-based logging set up before Tauri plugins begin initialization but we could only get the appropriate paths after Tauri starts. The solution is - rather than relying on Tauri's built-in path resolution which are only available after the app is created, we implemented a similar path resolution directly in `main.rs`: ```rust // Follows how `tauri` does this // 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_DESKTOP_IDENTIFIER)); #[cfg(not(target_os = "macos"))] let path = dirs::data_local_dir().map(|dir| dir.join(HOPPSCOTCH_DESKTOP_IDENTIFIER).join("logs")); path }; ``` This mimics Tauri's own path resolution logic to find the log directory before the Tauri application starts. Next, change `logger.rs` to accept a path rather than an `AppHandle`: ```rust pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> { std::fs::create_dir_all(log_dir)?; // ... logging setup ... Ok(LogGuard(guard)) } ``` So now, instead of managing the log guard within the `setup` function using `app_handle.manage()`, we return the guard to the caller, allowing it to be managed in the `main` function's scope: ```rust let Ok(LogGuard(guard)) = logger::setup(&log_file_path) else { eprint!("Failed to setup logging!"); println!("Starting Hoppscotch Desktop..."); return hoppscotch_desktop_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; ``` The `_guard` is important. Underscore is just for warnings, but the variable itself keeps the guard alive for the lifetime of the `main`. Since the guard is responsible for buffered logs to get flushed to disk. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-17 02:30:50 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hoppscotch#5026
No description provided.