{"record":{"id":"0d0d249379dc2724","repo":"astrid-runtime/astrid","slug":"configure-histogram-buckets-e","errorCode":null,"errorMessage":"configure histogram buckets: {e}","messagePattern":"configure histogram buckets: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/metrics.rs","lineNumber":128,"sourceCode":"/// on first call. Subsequent calls cannot fail.\npub fn install_recorder() -> anyhow::Result<PrometheusHandle> {\n    static HANDLE: Mutex<Option<PrometheusHandle>> = Mutex::new(None);\n    let mut guard = HANDLE\n        .lock()\n        .map_err(|e| anyhow::anyhow!(\"recorder lock poisoned: {e}\"))?;\n    if let Some(existing) = guard.as_ref() {\n        return Ok(existing.clone());\n    }\n    let handle = PrometheusBuilder::new()\n        // Set the per-route latency histogram's buckets explicitly.\n        // The `Suffix` matcher applies to every series whose name\n        // matches the metric base — same buckets for every\n        // (method, route, status) combo.\n        .set_buckets_for_metric(\n            Matcher::Suffix(METRIC_REQUEST_DURATION_SECONDS.to_string()),\n            DURATION_BUCKETS_SECONDS,\n        )\n        .map_err(|e| anyhow::anyhow!(\"configure histogram buckets: {e}\"))?\n        .install_recorder()\n        .map_err(|e| anyhow::anyhow!(\"install Prometheus recorder: {e}\"))?;\n\n    // Describe every metric the gateway emits so `/metrics` carries\n    // `# HELP` + `# TYPE` lines even before the series sees its\n    // first observation. Touch each counter once at zero so it\n    // appears in the scrape body — `metrics-exporter-prometheus`\n    // only renders series after they've been recorded against.\n    describe_counter!(\n        METRIC_REQUESTS_TOTAL,\n        Unit::Count,\n        \"Total HTTP requests by method+route+status.\"\n    );\n    describe_histogram!(\n        METRIC_REQUEST_DURATION_SECONDS,\n        Unit::Seconds,\n        \"Per-request handler latency by method+route+status.\"\n    );","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/metrics.rs#L110-L146","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify DURATION_BUCKETS_SECONDS is a non-empty, strictly increasing slice of finite floats","Check the metrics-exporter-prometheus version's set_buckets_for_metric requirements","Fix the metric-name Matcher constant if the metric was renamed","Pin/upgrade the exporter crate to a compatible version"],"exampleFix":"// before\nconst DURATION_BUCKETS_SECONDS: &[f64] = &[0.005, 0.01, 0.01, 0.1]; // duplicate bucket\n// after\nconst DURATION_BUCKETS_SECONDS: &[f64] = &[0.005, 0.01, 0.05, 0.1]; // strictly increasing","handlingStrategy":"validation","validationCode":"fn buckets_valid(b: &[f64]) -> bool {\n    !b.is_empty() && b.windows(2).all(|w| w[0] < w[1]) && b.iter().all(|v| v.is_finite() && *v > 0.0)\n}\nassert!(buckets_valid(DURATION_BUCKETS_SECONDS));","typeGuard":null,"tryCatchPattern":"match metrics::install_recorder() {\n    Ok(h) => h,\n    Err(e) if e.to_string().contains(\"configure histogram buckets\") => {\n        // fix DURATION_BUCKETS_SECONDS: must be strictly increasing, non-empty\n        Err(anyhow!(\"bad bucket config: {e}\"))\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep bucket slices strictly increasing and non-empty; validate in a unit test","Re-check exporter-crate docs when bumping metrics-exporter-prometheus versions","Centralize bucket constants so all histograms share validated values"],"tags":["prometheus","metrics","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}