nautechsystems/nautilus_trader · error · anyhow::Error

failed to request logging sync: {e}

Error message

failed to request logging sync: {e}

What it means

`sync_sender_to_disk` sends a `LogEvent::Sync` oneshot handshake to the logger thread to flush buffered log events, then waits for the acknowledgement. If the `tx.send` fails — meaning the logging thread's receiver was dropped (logger thread exited/shut down) or the channel is broken — this error is returned.

Source

Thrown at crates/common/src/logging/logger.rs:1315

        let lifecycle = LOGGER_LIFECYCLE.lock();

        if *lifecycle != LoggerLifecycle::Running {
            return Ok(());
        }

        let Some(tx) = LOGGER_TX.get() else {
            anyhow::bail!("Logging is running without a published sender");
        };

        sync_sender_to_disk(tx)
    }
}

#[cfg(not(all(feature = "simulation", madsim)))]
fn sync_sender_to_disk(tx: &std::sync::mpsc::Sender<LogEvent>) -> anyhow::Result<()> {
    let (done_tx, done_rx) = std::sync::mpsc::channel();
    tx.send(LogEvent::Sync(done_tx))
        .map_err(|e| anyhow::anyhow!("failed to request logging sync: {e}"))?;

    done_rx
        .recv()
        .map_err(|e| anyhow::anyhow!("failed to receive logging sync acknowledgement: {e}"))?
}

/// Logs a message with the given level, color, and component.
pub fn log<T: AsRef<str>>(level: LogLevel, color: LogColor, component: Ustr, message: T) {
    let color = Value::from(color as u8);

    match level {
        LogLevel::Off => {}
        LogLevel::Trace => {
            log::trace!(component = component.to_value(), color = color; "{}", message.as_ref());
        }
        LogLevel::Debug => {
            log::debug!(component = component.to_value(), color = color; "{}", message.as_ref());
        }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the LogGuard outlives all flush calls; flush before dropping the guard.
  2. Treat send-failure as 'logging already shut down' — handle gracefully and skip flushing instead of propagating.
  3. Check whether the logging thread panicked (look for earlier panics) and fix the event payload or sink that caused it.
  4. Sequence shutdown: flush logs first, then drop guard, then exit; avoid flushing from atexit/Destructor after teardown.

Example fix

// before
drop(guard);
logger.flush()?; // failed to request logging sync
// after
guard.flush()?; // flush while logging thread is alive
// then
drop(guard);
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: confirm the logging thread is alive before flushing
if !logging_thread_running() { return Ok(()); } // nothing to flush

Type guard

fn can_flush(tx: &Sender<LogEvent>) -> bool { !tx.is_closed() } // wrapper with capacity probe

Try / catch

match sync_sender_to_disk(&tx) {
    Err(e) if e.to_string().contains("failed to request logging sync") => {
        debug!("logger already shut down; skipping flush");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling flush/sync (logger flush API or guard drop-time flush) after the logging thread has already terminated: guard dropped while another reference triggers flush, logger thread panicked, or flush invoked during shutdown ordering races.

Common situations: Calling logger.flush() after shutdown/exit handlers ran; dropping the LogGuard then flushing from another thread; panics in the logging thread from malformed events; multi-node processes where one node's teardown closed the shared sender.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/67ece42a7f818453. Report an issue: GitHub.