libnyanpasu/clash-nyanpasu · error

no logger channel

Error message

no logger channel

What it means

refresh_logger forwards a reload signal over the global logger channel, but the channel (stored in Channel::globals()) is None because the tracing non-blocking logging pipeline was never initialized. Sending is impossible, so it bails.

Solutions

  1. Ensure init (logging setup) completes and stores the sender before refresh_logger is called.
  2. Make the channel a required step in the startup sequence; guard refresh with an 'initialized' flag.
  3. If refresh during teardown, ignore the missing channel instead of failing.
  4. In tests, initialize the logger or stub the channel.

Example fix

// before
refresh_logger(signal)?;
// after
if logging::is_initialized() {
    refresh_logger(signal)?;
} else {
    log::warn!("logger not initialized; skipping refresh");
}
Defensive patterns

Strategy: validation

Validate before calling

fn logger_ready() -> bool {
    Channel::globals().lock().0.is_some()
}
// call only when ready
if logger_ready() { refresh_logger(signal)?; }

Try / catch

match refresh_logger(signal) {
    Ok(()) => {},
    Err(e) if e.to_string() == "no logger channel" => {
        log::warn!("logger not initialized; skipping refresh");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling refresh_logger (during init or later log reconfiguration) before the logging subsystem initialized its sender, or after the worker guard was dropped and the channel cleared.

Common situations: Calling refresh very early in startup before init ran, test harnesses that skip logger initialization, or a shutdown path that already dropped the logger guard while a refresh is still attempted.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/74a0c96213035c70. Report an issue: GitHub.

Appendix: source

Thrown at backend/tauri/src/utils/init/logging.rs:40

pub type ReloadSignal = (Option<config::nyanpasu::LoggingLevel>, Option<usize>);

struct Channel(Option<Sender<ReloadSignal>>);
impl Channel {
    fn globals() -> &'static Mutex<Channel> {
        static CHANNEL: OnceLock<Mutex<Channel>> = OnceLock::new();
        CHANNEL.get_or_init(|| Mutex::new(Channel(None)))
    }
}

pub fn refresh_logger(signal: ReloadSignal) -> Result<()> {
    let channel = Channel::globals().lock();
    match &channel.0 {
        Some(sender) => {
            let _ = sender.send(signal);
            Ok(())
        }
        None => bail!("no logger channel"),
    }
}

fn get_file_appender(max_files: usize) -> Result<(NonBlocking, WorkerGuard)> {
    let log_dir = dirs::app_logs_dir().unwrap();
    let file_appender = tracing_appender::rolling::Builder::new()
        .filename_prefix("clash-nyanpasu")
        .filename_suffix("app.log")
        .rotation(Rotation::DAILY)
        .max_log_files(max_files)
        .build(log_dir)?;
    Ok(tracing_appender::non_blocking(file_appender))
}

/// initial instance global logger
pub fn init() -> Result<()> {
    let log_dir = dirs::app_logs_dir().unwrap();
    if !log_dir.exists() {

View on GitHub (pinned to f7dbce2997)