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 mapView on GitHub (pinned to f4178619ac)
Solutions
- Initialize logging exactly once per process: call init from main or a single setup path.
- Guard shared or test setup with std::sync::Once so repeated calls are no-ops.
- In tests, use the provided zlog::init_test() entry point, which is designed for test binaries.
- 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
- Call logging init from one place (main, or one shared test fixture).
- Guard any reachable-from-multiple-entry-points init with Once.
- Use zlog::init_test() in tests instead of raw filter init.
- Remember the second call mutates the level static before it panics — prevent, do not catch.
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
- database not initialized
- row out of range
- invalid display row {}
- blocking sender returned without value
- anchor's path was never added to multibuffer
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/9f6b704935cb4e16.
Report an issue: GitHub.