astrid-runtime/astrid · error · anyhow::Error

configure histogram buckets

Error message

configure histogram buckets: {e}

What it means

install_recorder configures explicit histogram buckets for the request-duration metric via PrometheusBuilder::set_buckets_for_metric before installing the recorder. If the prometheus exporter builder rejects the bucket configuration (invalid matcher or bad bucket values), the error is wrapped as 'configure histogram buckets'.

Solutions

  1. Verify DURATION_BUCKETS_SECONDS is a non-empty, strictly increasing slice of finite floats
  2. Check the metrics-exporter-prometheus version's set_buckets_for_metric requirements
  3. Fix the metric-name Matcher constant if the metric was renamed
  4. Pin/upgrade the exporter crate to a compatible version

Example fix

// before
const DURATION_BUCKETS_SECONDS: &[f64] = &[0.005, 0.01, 0.01, 0.1]; // duplicate bucket
// after
const DURATION_BUCKETS_SECONDS: &[f64] = &[0.005, 0.01, 0.05, 0.1]; // strictly increasing
Defensive patterns

Strategy: validation

Validate before calling

fn buckets_valid(b: &[f64]) -> bool {
    !b.is_empty() && b.windows(2).all(|w| w[0] < w[1]) && b.iter().all(|v| v.is_finite() && *v > 0.0)
}
assert!(buckets_valid(DURATION_BUCKETS_SECONDS));

Try / catch

match metrics::install_recorder() {
    Ok(h) => h,
    Err(e) if e.to_string().contains("configure histogram buckets") => {
        // fix DURATION_BUCKETS_SECONDS: must be strictly increasing, non-empty
        Err(anyhow!("bad bucket config: {e}"))
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: set_buckets_for_metric(Matcher::Suffix(METRIC_REQUEST_DURATION_SECONDS), DURATION_BUCKETS_SECONDS) returns Err — e.g. buckets not strictly increasing, empty bucket slice, or an invalid Matcher for the builder version in use.

Common situations: Editing DURATION_BUCKETS_SECONDS and introducing a non-monotonic or duplicate bucket; upgrading metrics-exporter-prometheus to a version with stricter validation; typo in the metric name/matcher constant.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/0d0d249379dc2724. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-gateway/src/metrics.rs:128

/// on first call. Subsequent calls cannot fail.
pub fn install_recorder() -> anyhow::Result<PrometheusHandle> {
    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."
    );

View on GitHub (pinned to affd8760f4)