zed-industries/zed · error

Environment filter cannot be initialized twice

Error message

Environment filter cannot be initialized twice

What it means

zlog, Zed's logging stack, stores the process-wide env filter in a set-once static; init_env_filter panics if the filter is already installed (crates/zlog/src/filter.rs:53), because log filtering is configured exactly once per process. Note the global level maximum is stored before the set, so a second call mutates shared state before it panics — one more reason re-init must be prevented, not caught.

Source

Thrown at crates/zlog/src/filter.rs:53

pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);

const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
    ("zbus", log::LevelFilter::Warn),
    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
    ("naga::back::spv::writer", log::LevelFilter::Warn),
    // usvg prints a lot of warnings on rendering an SVG with partial errors, which
    // can happen a lot with the SVG preview
    ("usvg::parser", log::LevelFilter::Error),
    ("pet", log::LevelFilter::Warn),
];

pub fn init_env_filter(filter: env_config::EnvFilter) {
    if let Some(level_max) = filter.level_global {
        LEVEL_ENABLED_MAX_STATIC.store(level_max as u8, Ordering::Release)
    }
    if ENV_FILTER.set(filter).is_err() {
        panic!("Environment filter cannot be initialized twice");
    }
}

pub fn is_possibly_enabled_level(level: log::Level) -> bool {
    level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Acquire)
}

pub fn is_scope_enabled(
    scope: &ScopeRef<'_>,
    module_path: Option<&str>,
    level: log::Level,
) -> bool {
    // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG
    if !is_possibly_enabled_level(level) {
        // [FAST PATH]
        // if the message is above the maximum enabled log level
        // (where error < warn < info etc) then disable without checking
        // scope map

View on GitHub (pinned to f4178619ac)

Solutions

  1. Initialize logging exactly once per process: call init from main or a single setup path.
  2. Guard shared or test setup with std::sync::Once so repeated calls are no-ops.
  3. In tests, use the provided zlog::init_test() entry point, which is designed for test binaries.
  4. For runtime changes, adjust dynamic level/scope controls instead of re-calling init.

Example fix

// before: init runs per test / per call site
fn setup_logging() {
    zlog::filter::init_env_filter(filter);
}

// after: once per process
static INIT_LOGGING: std::sync::Once = std::sync::Once::new();
fn setup_logging() {
    INIT_LOGGING.call_once(|| zlog::filter::init_env_filter(filter));
}
Defensive patterns

Strategy: validation

Validate before calling

static INIT_LOGGING: std::sync::Once = std::sync::Once::new();

fn setup_logging() {
    INIT_LOGGING.call_once(|| zlog::filter::init_env_filter(filter));
}

Prevention

When it happens

Trigger: Calling zlog::filter::init_env_filter (directly or via a zlog init entry point) a second time in the same process: per-test logging init when tests share a process, a library and its host both running init, or a re-init attempt after config reload.

Common situations: cargo test suites where each test calls the logging setup helper; plugins or embedding hosts that also initialize zlog; code that treats logging init as idempotent.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/9f6b704935cb4e16. Report an issue: GitHub.