Skip to main content

soma_observability/
metrics.rs

1//! Prometheus metrics recorder and renderer.
2//!
3//! The server installs a single global Prometheus recorder at startup via
4//! [`init`], then exposes the gathered metrics over a `/metrics` route that
5//! calls [`render`]. Emitting code anywhere in the workspace uses the
6//! lightweight `metrics` facade (`metrics::counter!`, `metrics::histogram!`);
7//! when no recorder is installed (stdio mode, CLI, tests) those macros are
8//! cheap no-ops, so emitting metrics is always safe.
9//!
10//! Everything here is fail-soft: a metrics problem must never take the server
11//! down, so install failures are logged and swallowed and `/metrics` simply
12//! reports "not initialized" until a recorder exists.
13
14use std::sync::OnceLock;
15
16use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
17
18static HANDLE: OnceLock<PrometheusHandle> = OnceLock::new();
19
20/// Install the global Prometheus recorder. Idempotent: a second call is a no-op,
21/// and a failed install is logged rather than propagated so startup continues.
22pub fn init() {
23    if HANDLE.get().is_some() {
24        return;
25    }
26    match PrometheusBuilder::new().install_recorder() {
27        Ok(handle) => {
28            // Ignore a race where another thread set it first — either handle works.
29            let _ = HANDLE.set(handle);
30        }
31        Err(error) => {
32            tracing::warn!(%error, "failed to install Prometheus recorder; /metrics will be empty");
33        }
34    }
35}
36
37/// Render the current metrics in Prometheus text exposition format, or `None`
38/// if no recorder has been installed (so the route can answer 503).
39pub fn render() -> Option<String> {
40    HANDLE.get().map(PrometheusHandle::render)
41}
42
43/// Whether a recorder has been installed. Primarily for tests and diagnostics.
44pub fn is_installed() -> bool {
45    HANDLE.get().is_some()
46}
47
48#[cfg(test)]
49#[path = "metrics_tests.rs"]
50mod tests;