nautechsystems/nautilus_trader · error · anyhow::Error

Logging already initialized but new guard could not be creat

Error message

Logging already initialized but new guard could not be created

What it means

When the logger lifecycle is already `Running`, an init call simply hands back a locked LogGuard referencing the global sender. `LogGuard::new_locked()` returns None if the guard's internal state (global sender) cannot be established, and the code surfaces that as this error: logging is up, but a second guard handle could not be created.

Source

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

    /// # Errors
    ///
    /// Returns an error if the logger fails to register or initialize the background thread.
    #[cfg_attr(
        not(all(feature = "simulation", madsim)),
        expect(clippy::needless_pass_by_value)
    )]
    pub fn init_with_config(
        trader_id: TraderId,
        instance_id: UUID4,
        config: LoggerConfig,
        file_config: FileWriterConfig,
    ) -> anyhow::Result<LogGuard> {
        let mut lifecycle = LOGGER_LIFECYCLE.lock();

        match *lifecycle {
            LoggerLifecycle::Running => {
                return LogGuard::new_locked().ok_or_else(|| {
                    anyhow::anyhow!(
                        "Logging already initialized but new guard could not be created"
                    )
                });
            }
            LoggerLifecycle::Terminated => {
                anyhow::bail!("Logging has been shut down and cannot be re-initialized");
            }
            LoggerLifecycle::Uninitialized => {}
        }

        let (tx, rx) = std::sync::mpsc::channel::<LogEvent>();
        let filter_policy = FilterPolicy::from_config(&config);

        #[cfg(not(all(feature = "simulation", madsim)))]
        let handle = std::thread::Builder::new()
            .name(LOGGING.to_string())
            .spawn({
                let config = config.clone();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Initialize logging once at process start and reuse/clone the returned LogGuard instead of re-initializing.
  2. Keep the original guard alive for the process lifetime; drop it only when you intend shutdown.
  3. In forked children, fully re-create logging state (or avoid forking after init) so the global sender is valid.
  4. If re-init is required, shut down the running logger first (drop guard) and handle the Terminated lifecycle path.

Example fix

// before
let guard1 = init_logging(...)?;
let guard2 = init_logging(...)?; // Running path fails here
// after
let guard = init_logging(...)?; // hold for process lifetime
std::mem::forget(guard); // or store globally; never re-init
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: only init when not already running
if logging_is_running() { return Ok(existing_guard()); }

Type guard

fn ensure_guard(g: Option<LogGuard>) -> anyhow::Result<LogGuard> {
    g.ok_or_else(|| anyhow::anyhow!("no log guard available"))
}

Try / catch

match init_logging(cfg) {
    Ok(g) => g,
    Err(e) if e.to_string().contains("already initialized") => Ok(shared_guard()),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling logging init (e.g. nautilus_pyo3 init_logging or the Rust init) while a previous logger is still running, in a process where the global sender/static state is unavailable or was never fully established — e.g. after fork in a child process, or across unusual embed boundaries where statics did not carry over.

Common situations: Re-initializing logging on every script run in one interpreter; embedding multiple node instances in one process; Unix fork() after logger init leaving stale statics in the child; conflicting logging bootstrap between host app and library.

Related errors


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