# How Clash Nyanpasu Structures Its Logging System, Error Handling, and Panic Hooks

> Explore Clash Nyanpasu's logging system, error handling, and panic hooks. Discover structured logging, dynamic log levels, and robust panic recovery with backtraces and user-friendly error dialogs.

- Repository: [Nyanpasu/clash-nyanpasu](https://github.com/libnyanpasu/clash-nyanpasu)
- Tags: internals
- Published: 2026-03-06

---

**Clash Nyanpasu uses the `tracing` ecosystem for structured logging with rotating file appenders, implements dynamic log level reloading via channels, and registers a global panic hook that captures backtraces, logs fatal errors to disk, and displays native system dialogs to users.**

The logging system and error handling architecture in Clash Nyanpasu (the Tauri-based desktop proxy client) is designed for production reliability and developer observability. According to the `libnyanpasu/clash-nyanpasu` source code, the backend combines Rust's `tracing` library with custom file rotation logic, while the frontend uses TypeScript utilities for development-time console output.

## Logging Architecture Overview

### The Tracing Ecosystem Setup

The core initialization happens in [`backend/tauri/src/utils/init/logging.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/utils/init/logging.rs). The `init()` function establishes a global subscriber that combines multiple layers:

- **File Layer**: A JSON-formatted appender that writes to daily-rotating log files (`clash-nyanpasu-*.log`) with configurable retention
- **Console Layer** (debug builds only): Pretty, ANSI-colored output to stdout/stderr using `tracing_subscriber::fmt::layer()`
- **EnvFilter**: Parses `RUST_LOG` or `LOG_LEVEL` environment variables to control verbosity per module

The function first ensures the logs directory exists using `dirs::app_logs_dir()`, then constructs the subscriber with `tracing::subscriber::set_global_default()`.

### File Rotation and Appender Configuration

Log rotation is handled by `get_file_appender(max_files: usize)` in the same module. This creates a non-blocking writer that:

1. Generates filenames with daily timestamps (`clash-nyanpasu-YYYY-MM-DD.log`)
2. Maintains a maximum of `max_files` historical log files, deleting older entries automatically
3. Uses `tracing_appender::rolling::Rotation::DAILY` for the underlying rotation logic

The appender is wrapped with `tracing_subscriber::fmt::layer().json()` to ensure structured logging output suitable for debugging production issues.

### Dynamic Log Level Reloading

Clash Nyanpasu supports changing log levels at runtime without restarting the application. This is implemented via a global `mpsc::channel<ReloadSignal>`:

```rust
pub struct ReloadSignal {
    pub level: Option<LoggingLevel>,
    pub max_files: Option<usize>,
}

```

When `refresh_logger((Some(new_level), Some(new_max_files)))` is called—typically after the user changes settings in the UI—the function sends a signal through the channel. A background thread spawned during initialization receives this signal and updates the `EnvFilter` layer and file appender accordingly.

## Error Handling and Panic Hooks

### Global Panic Hook Registration

In [`backend/tauri/src/lib.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/lib.rs), the application installs a custom panic hook immediately upon startup using `std::panic::set_hook()`:

```rust
std::panic::set_hook(Box::new(move |panic_info| {
    // Capture payload and location
    let payload = panic_info.payload();
    let location = panic_info.location().map(|l| l.to_string());
    
    // Log the panic via tracing
    tracing::event!(
        tracing::Level::ERROR,
        panic.payload = ?payload,
        panic.location = ?location,
        panic.backtrace = backtrace.as_ref().map(tracing::field::display),
        "A panic occurred"
    );
    
    // Show user-facing dialog
    utils::dialog::panic_dialog(&msg);
}));

```

This hook intercepts all unrecoverable errors before the application terminates.

### Structured Panic Logging

When a panic occurs, the hook captures:

- **Payload**: The panic message (often an `anyhow::Error` or string slice)
- **Location**: File path and line number where the panic originated
- **Backtrace**: Full stack trace when `RUST_BACKTRACE=1` is set in the environment

These fields are emitted as a structured `tracing::event!` at the ERROR level, ensuring they are written to the rotating log file with JSON formatting. This allows developers to correlate user-reported crashes with specific code locations and stack traces.

### User Notification via Native Dialogs

After logging the panic, the hook calls `utils::dialog::panic_dialog(msg)` defined in [`backend/tauri/src/utils/dialog.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/utils/dialog.rs). This function uses Tauri's `MessageDialog` API to display a platform-native error dialog containing:

- The panic message
- A localized title fetched via `t!("dialog.panic")` (i18n support)
- Standard system dialog buttons (typically "OK")

This ensures users are immediately informed of fatal errors rather than experiencing a silent crash, while the detailed technical information is preserved in the log files for debugging.

## Integration with Configuration System

The logging system integrates tightly with Clash Nyanpasu's configuration management. During the initialization sequence in `logging::init()`, a dedicated thread spawns to synchronize logger settings with the user's configuration:

```rust
std::thread::spawn(move || {
    let cfg = Config::verge();
    let level = cfg.latest().get_log_level();
    let max_files = cfg.latest().max_log_files;
    let _ = refresh_logger((Some(level), max_files));
});

```

This ensures that:
- The log level respects the user's preference from the previous session immediately upon startup
- The number of retained log files matches the configured rotation policy
- Changes made in the settings UI trigger `refresh_logger()` to update the active logger without requiring an application restart

## Code Examples

### Initializing the Logger

To start the logging system in a Tauri application context:

```rust
use backend::tauri::utils::init::logging;

fn main() -> anyhow::Result<()> {
    // Initialize global tracing subscriber with file rotation
    logging::init()?;
    
    // Now tracing macros work throughout the application
    tracing::info!("Application started successfully");
    
    Ok(())
}

```

### Refreshing Log Configuration

When implementing a settings panel that allows users to change log verbosity:

```rust
use backend::tauri::utils::init::logging::{refresh_logger, LoggingLevel};

fn on_settings_changed(new_level: LoggingLevel, max_retention: usize) {
    // Send reload signal through the global channel
    let result = refresh_logger((Some(new_level), Some(max_retention)));
    
    match result {
        Ok(_) => tracing::info!("Log configuration updated successfully"),
        Err(e) => tracing::error!("Failed to refresh logger: {}", e),
    }
}

```

### Testing Panic Handling

To verify the panic hook captures and logs correctly during development:

```rust
fn test_panic_recovery() {
    // This will trigger the global hook
    std::panic::set_hook(Box::new(|info| {
        tracing::error!("Test panic captured: {:?}", info);
    }));
    
    // Intentional panic to test the hook
    panic!("simulated fatal error");
}

```

When executed with `RUST_BACKTRACE=1`, this produces a structured log entry containing the backtrace and displays the native error dialog.

## Summary

- **Structured Logging**: Clash Nyanpasu uses `tracing` with JSON formatting and daily file rotation, configured in [`backend/tauri/src/utils/init/logging.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/utils/init/logging.rs).
- **Dynamic Reloading**: A global `mpsc` channel enables runtime updates to log levels and retention policies without restarting the application.
- **Panic Hooks**: The global panic hook in [`backend/tauri/src/lib.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/lib.rs) captures fatal errors, logs structured panic information including backtraces, and invokes `utils::dialog::panic_dialog()` to notify users via native system dialogs.
- **Configuration Integration**: Logger settings synchronize automatically with the user's configuration from `Config::verge()`, ensuring preferences persist across sessions.

## Frequently Asked Questions

### How does Clash Nyanpasu handle log file rotation?

The application creates a daily-rotating file appender via `get_file_appender(max_files)` in [`backend/tauri/src/utils/init/logging.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/utils/init/logging.rs). This generates files named `clash-nyanpasu-YYYY-MM-DD.log` and automatically deletes older files when the count exceeds the configured `max_files` limit. The rotation uses `tracing_appender::rolling::Rotation::DAILY` to ensure non-blocking I/O operations.

### Can I change the log level without restarting Clash Nyanpasu?

Yes. The logging system supports dynamic reloading through the `refresh_logger()` function. When you modify settings in the UI, the application sends a `ReloadSignal` through a global `mpsc` channel to a background thread. This thread updates the `EnvFilter` layer with the new log level and adjusts the file appender's retention settings immediately, without requiring an application restart.

### What happens when Clash Nyanpasu encounters a fatal panic?

When a panic occurs, the custom hook installed in [`backend/tauri/src/lib.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/lib.rs) intercepts the error before termination. It captures the panic payload, source location, and optional backtrace (when `RUST_BACKTRACE=1` is set), then emits a structured `tracing::event!` at the ERROR level to ensure the crash is recorded in the rotating log file. Finally, it calls `utils::dialog::panic_dialog()` to display a native system dialog informing the user of the unexpected error, preventing silent crashes while preserving diagnostic data for developers.