astrid-runtime/astrid · error · anyhow::Error
install Prometheus recorder
Error message
install Prometheus recorder: {e} What it means
After configuring buckets, install_recorder calls PrometheusBuilder::install_recorder() to register the global metrics recorder. If the exporter cannot install (typically a recorder/collector registration conflict in the prometheus registry), the failure is wrapped as 'install Prometheus recorder'. Because the handle is cached in a static, this can only fail on the first call.
Solutions
- Ensure install_recorder() is called exactly once, early in process startup, before any other metrics installation
- Remove or unify competing global recorder installations (only one global recorder is allowed per process)
- Route all metrics setup through install_recorder so the idempotent static handle is used
- In tests, isolate recorder installation to one test or use a single shared handle
Example fix
// before prometheus::default_registry(); // or another lib installs a global recorder first let handle = metrics::install_recorder()?; // after let handle = metrics::install_recorder()?; // single, idempotent installation point
Defensive patterns
Strategy: try-catch
Try / catch
match metrics::install_recorder() {
Ok(h) => h,
Err(e) if e.to_string().contains("install Prometheus recorder") => {
// another global recorder was installed first; find and remove the competing install
Err(anyhow!("global recorder conflict: {e}"))
}
Err(e) => return Err(e),
} Prevention
- Install the global recorder exactly once, at the very start of main
- Ban other global recorder installations (grep for set_boxed_recorder / install in deps)
- Use the idempotent static-handle path for all later metric setup
When it happens
Trigger: First invocation of install_recorder() where PrometheusBuilder::install_recorder() fails — usually because a global recorder is already installed (e.g. another exporter such as the prometheus crate's set_boxed_recorder was used earlier in the process).
Common situations: Two metrics libraries both trying to install the global recorder; tests running in the same process where another test installed a recorder first; calling a different install path (prometheus::default_registry tricks) before this one.
Related errors
- configure histogram buckets
- a corpus produced no chunks
- a representation record must cover at least one logical…
- authoritative principal store is unavailable
- Failed to boot Kernel
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/d1693383acc5ded7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/metrics.rs:130
static HANDLE: Mutex<Option<PrometheusHandle>> = Mutex::new(None);
let mut guard = HANDLE
.lock()
.map_err(|e| anyhow::anyhow!("recorder lock poisoned: {e}"))?;
if let Some(existing) = guard.as_ref() {
return Ok(existing.clone());
}
let handle = PrometheusBuilder::new()
// Set the per-route latency histogram's buckets explicitly.
// The `Suffix` matcher applies to every series whose name
// matches the metric base — same buckets for every
// (method, route, status) combo.
.set_buckets_for_metric(
Matcher::Suffix(METRIC_REQUEST_DURATION_SECONDS.to_string()),
DURATION_BUCKETS_SECONDS,
)
.map_err(|e| anyhow::anyhow!("configure histogram buckets: {e}"))?
.install_recorder()
.map_err(|e| anyhow::anyhow!("install Prometheus recorder: {e}"))?;
// Describe every metric the gateway emits so `/metrics` carries
// `# HELP` + `# TYPE` lines even before the series sees its
// first observation. Touch each counter once at zero so it
// appears in the scrape body — `metrics-exporter-prometheus`
// only renders series after they've been recorded against.
describe_counter!(
METRIC_REQUESTS_TOTAL,
Unit::Count,
"Total HTTP requests by method+route+status."
);
describe_histogram!(
METRIC_REQUEST_DURATION_SECONDS,
Unit::Seconds,
"Per-request handler latency by method+route+status."
);
describe_counter!(
METRIC_AUTH_FAILURES_TOTAL,View on GitHub (pinned to affd8760f4)