{"record":{"id":"21835858ae31f197","repo":"quickwit-oss/quickwit","slug":"prometheus-metrics-renderer-is-already-installed","errorCode":null,"errorMessage":"Prometheus metrics renderer is already installed","messagePattern":"Prometheus metrics renderer is already installed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-telemetry-exporters/src/prometheus/metrics.rs","lineNumber":38,"sourceCode":"    Matcher, PrometheusBuilder, PrometheusHandle, PrometheusRecorder,\n};\n\nstatic PROMETHEUS_HANDLE: OnceLock<PrometheusHandle> = OnceLock::new();\n\npub(crate) fn build_recorder() -> anyhow::Result<PrometheusRecorder> {\n    let mut prometheus_builder = PrometheusBuilder::new();\n    for (name, buckets) in quickwit_metrics::histogram_buckets() {\n        prometheus_builder = prometheus_builder\n            .set_buckets_for_metric(Matcher::Full(name.to_string()), &buckets)\n            .with_context(|| {\n                format!(\"failed to configure Prometheus histogram buckets for `{name}`\")\n            })?;\n    }\n    let prometheus_recorder = prometheus_builder.build_recorder();\n    let prometheus_handle = prometheus_recorder.handle();\n    PROMETHEUS_HANDLE\n        .set(prometheus_handle.clone())\n        .map_err(|_| anyhow::anyhow!(\"Prometheus metrics renderer is already installed\"))?;\n    spawn_prometheus_upkeep(prometheus_handle).map_err(anyhow::Error::msg)?;\n    Ok(prometheus_recorder)\n}\n\npub fn text_payload() -> Result<String, String> {\n    let handle = PROMETHEUS_HANDLE\n        .get()\n        .ok_or_else(|| \"Prometheus metrics rendering is not installed yet\".to_string())?;\n    Ok(handle.render())\n}\n\nfn spawn_prometheus_upkeep(handle: PrometheusHandle) -> Result<(), String> {\n    // Quickwit serves the existing `/metrics` route itself, so we build only the\n    // Prometheus recorder instead of using the exporter's HTTP listener. That lower-level\n    // API does not spawn the upkeep task that periodically drains histogram buffers.\n    std::thread::Builder::new()\n        .name(\"telemetry-exporter-prometheus-upkeep\".to_string())\n        .spawn(move || {","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-telemetry-exporters/src/prometheus/metrics.rs#L20-L56","documentation":"build_recorder creates the Prometheus recorder and stores its metrics render handle into the process-global PROMETHEUS_HANDLE OnceCell. If that cell already holds a handle, installation fails with this error, because the Prometheus exposition endpoint (`text_payload`) would otherwise read from the wrong/absent renderer. Same one-time-only constraint as the global metrics recorder.","triggerScenarios":"Calling build_recorder twice in one process (double bootstrap of telemetry, repeated service startup within a process, or multiple tests each building a Prometheus recorder).","commonSituations":"Integration test suites where each test spins up a serve/telemetry stack; embedding Quickwit and initializing Prometheus metrics twice; accidental duplicate call in custom bootstrap code.","solutions":["Call build_recorder only once per process lifetime","Guard with std::sync::Once or check if the Prometheus endpoint already responds before rebuilding","In tests, share one telemetry setup across the test binary instead of per-test init"],"exampleFix":"// before\neach_test(|| { build_recorder().unwrap(); })\n// after\nstatic ONCE: Once = Once::new();\nfn setup_prometheus() { ONCE.call_once(|| build_recorder().unwrap()); }","handlingStrategy":"validation","validationCode":"// Rust: guard global handle installation\nstatic PROM_INIT: Once = Once::new();\nfn ensure_prometheus() { PROM_INIT.call_once(|| build_recorder().expect(\"prometheus init\")); }","typeGuard":null,"tryCatchPattern":"if let Err(e) = build_recorder() {\n    if e.to_string().contains(\"already installed\") { /* reuse existing renderer */ }\n    else { return Err(e); }\n}","preventionTips":["Build the Prometheus recorder once per process","Share one telemetry bootstrap across all tests in a binary","Do not re-run service bootstrap code that installs Prometheus handles"],"tags":["metrics","prometheus","initialization","global-state"],"backgroundTag":"module-init-failed","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}