api_tools/server/axum/handlers/
prometheus.rs1use crate::server::axum::response::ApiError;
4use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle};
5
6pub const DEFAULT_DURATION_BUCKETS: &[f64] = &[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0];
9
10pub struct PrometheusHandler {}
12
13impl PrometheusHandler {
14 pub fn get_handle() -> Result<PrometheusHandle, ApiError> {
17 Self::get_handle_with_buckets(DEFAULT_DURATION_BUCKETS)
18 }
19
20 pub fn get_handle_with_buckets(buckets: &[f64]) -> Result<PrometheusHandle, ApiError> {
25 PrometheusBuilder::new()
26 .set_buckets_for_metric(Matcher::Full("http_requests_duration_seconds".to_string()), buckets)
27 .map_err(|err| ApiError::InternalServerError(err.to_string()))?
28 .install_recorder()
29 .map_err(|err| ApiError::InternalServerError(err.to_string()))
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn default_duration_buckets_are_monotonically_increasing() {
39 assert!(!DEFAULT_DURATION_BUCKETS.is_empty());
40 for pair in DEFAULT_DURATION_BUCKETS.windows(2) {
41 assert!(pair[0] < pair[1], "buckets must be strictly increasing: {pair:?}");
42 }
43 }
44
45 #[test]
51 fn handler_install_recorder_lifecycle() {
52 let handle =
54 PrometheusHandler::get_handle_with_buckets(&[0.001, 0.01, 0.1]).expect("first install should succeed");
55 let _rendered = handle.render();
57
58 let err = PrometheusHandler::get_handle().expect_err("second install must fail");
61 assert!(matches!(err, ApiError::InternalServerError(_)));
62
63 let err = PrometheusHandler::get_handle_with_buckets(&[0.5, 1.0]).expect_err("third install must fail");
64 assert!(matches!(err, ApiError::InternalServerError(_)));
65 }
66}