{"record":{"id":"6d3ba0cd7d7a08c8","repo":"gitbutlerapp/gitbutler","slug":"failed-to-set-subscriber","errorCode":null,"errorMessage":"failed to set subscriber","messagePattern":"failed to set subscriber","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-tauri/src/logs.rs","lineNumber":90,"sourceCode":"                tracing_forest::ForestLayer::from(\n                    tracing_forest::printer::PrettyPrinter::new().writer(std::io::stdout),\n                )\n                .with_filter(filter_fn(move |meta| should_log(log_level, meta))),\n            ),\n        )\n    } else {\n        set_global_default(\n            subscriber.with(\n                // subscriber that writes spans to stdout\n                tracing_subscriber::fmt::layer()\n                    .event_format(format_for_humans)\n                    .with_ansi(use_colors_in_logs)\n                    .with_span_events(FmtSpan::CLOSE)\n                    .with_filter(filter_fn(move |meta| should_log(log_level, meta))),\n            ),\n        )\n    }\n    .expect(\"failed to set subscriber\");\n}\n\n/// This function is much like `LevelFilter`, but it also filters based on the module path.\n/// This is necessary, strangely enough, in release builds only, but is the same in the `but` CLI builds as well.\n/// It is pretty much the same as `LevelFilter` in debug builds.\nfn should_log(level: Option<Level>, meta: &tracing::Metadata<'_>) -> bool {\n    let Some(level) = level else {\n        return false;\n    };\n    if *meta.level() > level {\n        return false;\n    }\n    if level > Level::DEBUG {\n        return true;\n    }\n    meta.module_path().is_none_or(|p| {\n        p.starts_with(\"gitbutler_\") || p.starts_with(\"but::\") || p.starts_with(\"but_\")\n    })","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-tauri/src/logs.rs#L72-L108","documentation":"logs::init() finishes by installing a global tracing subscriber via tracing::subscriber::set_global_default(...).expect(\"failed to set subscriber\"). set_global_default returns Err precisely when a global default subscriber is already installed - a process allows exactly one. The panic therefore means logs::init ran twice, or another component claimed the global subscriber first (e.g. tracing_subscriber::fmt().init() in a dependency or test harness).","triggerScenarios":"Calling logs::init twice (double setup, retry/re-entry logic), or initializing tracing elsewhere first: a dependency calling tracing_subscriber::fmt().init(), a test harness with its own subscriber, or another logging plugin registering a global default before the Tauri setup hook runs.","commonSituations":"E2E test harnesses that install their own subscriber before launching the app, refactors adding early logging, or duplicated initialization after an error-retry path re-enters setup.","solutions":["Ensure logs::init runs exactly once per process, at app startup","Treat a failed install as non-fatal: log a warning and keep the existing subscriber (see exampleFix)","In tests, use thread-local set_default/with_default instead of global installation","If a plugin also configures logging (e.g. tauri_plugin_log), order initialization so logs::init runs first or drop one of the two"],"exampleFix":"// before\nset_global_default(subscriber).expect(\"failed to set subscriber\");\n\n// after\nif set_global_default(subscriber).is_err() {\n    eprintln!(\"warning: global tracing subscriber already set; skipping re-init\");\n}","handlingStrategy":"validation","validationCode":"// One-shot guard around logging init at the host boundary\nstatic LOGS_INIT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);\nif LOGS_INIT.swap(true, std::sync::atomic::Ordering::SeqCst) {\n    return; // already initialized - a second set_global_default would panic\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Install the global tracing subscriber exactly once per process","Use thread-local tracing::subscriber::set_default in tests, never fmt().init() in libraries","Order plugin logging vs logs::init deliberately; check for duplicate setup entry points"],"tags":["rust","tracing","subscriber","global-state","logging","startup","panic"],"backgroundTag":"tracing-subscriber-already-set","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}