Skip to main content

web/
telemetry.rs

1//! Metric names and recorder setup.
2//!
3//! Names carry the `nomisync_` prefix because the series land in a
4//! VictoriaMetrics shared with every other app on the cluster: a bare
5//! `http_requests_total` would collide with anything else exporting the obvious
6//! name, and an ad-hoc query without a namespace filter would silently mix them.
7//! Keeping the names here as constants means the exporter and any test that
8//! asserts on them cannot drift apart.
9
10use metrics_exporter_prometheus::{BuildError, Matcher, PrometheusBuilder, PrometheusHandle};
11
12/// Requests served, labelled by method, matched path and status.
13pub const HTTP_REQUESTS: &str = "nomisync_http_requests_total";
14
15/// Request latency in seconds, same labels, bucketed by [`LATENCY_BUCKETS`].
16pub const HTTP_REQUEST_DURATION: &str = "nomisync_http_requests_duration_seconds";
17
18/// Auth attempts refused by the per-address throttle.
19pub const AUTH_THROTTLED: &str = "nomisync_auth_throttled_total";
20
21/// Web-request latencies: sub-10ms to 10s, which spans a cached page render and
22/// a slow report query.
23const LATENCY_BUCKETS: &[f64] = &[
24    0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
25];
26
27/// Install the process-wide Prometheus recorder.
28///
29/// # Errors
30/// [`BuildError`] if the bucket configuration is rejected or a recorder is
31/// already installed — both are startup faults, reported rather than panicked.
32pub fn install_recorder() -> Result<PrometheusHandle, BuildError> {
33    PrometheusBuilder::new()
34        .set_buckets_for_metric(
35            Matcher::Full(HTTP_REQUEST_DURATION.to_string()),
36            LATENCY_BUCKETS,
37        )?
38        .install_recorder()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn names_carry_the_app_prefix() {
47        for name in [HTTP_REQUESTS, HTTP_REQUEST_DURATION] {
48            assert!(
49                name.starts_with("nomisync_"),
50                "{name} would collide with other apps in the shared VictoriaMetrics"
51            );
52        }
53    }
54
55    #[test]
56    fn recorder_renders_the_registered_series() {
57        let Ok(handle) = install_recorder() else {
58            // A recorder is global and install-once; another test in this binary
59            // may already hold it. Nothing to assert then.
60            return;
61        };
62        metrics::counter!(HTTP_REQUESTS, &[("method", "GET")]).increment(1);
63        let rendered = handle.render();
64        assert!(
65            rendered.contains(HTTP_REQUESTS),
66            "exporter did not render {HTTP_REQUESTS}"
67        );
68    }
69}