viz_handlers/
prometheus.rs

1//! [OpenTelemetry(OTEL) Prometheus Exporter][OTEL].
2//!
3//! [OTEL]: https://docs.rs/opentelemetry-prometheus
4
5use http_body_util::Full;
6use prometheus::{Encoder, TextEncoder};
7
8use viz_core::{
9    header::{HeaderValue, CONTENT_TYPE},
10    Handler, IntoResponse, Request, Response, Result, StatusCode,
11};
12
13#[doc(inline)]
14pub use opentelemetry_prometheus::ExporterBuilder;
15#[doc(inline)]
16pub use prometheus::Registry;
17
18/// The [`Registry`] wrapper.
19#[derive(Clone, Debug)]
20pub struct Prometheus {
21    registry: Registry,
22}
23
24impl Prometheus {
25    /// Creates a new [`Prometheus`].
26    #[must_use]
27    pub const fn new(registry: Registry) -> Self {
28        Self { registry }
29    }
30}
31
32#[viz_core::async_trait]
33impl Handler<Request> for Prometheus {
34    type Output = Result<Response>;
35
36    async fn call(&self, _: Request) -> Self::Output {
37        let metric_families = self.registry.gather();
38        let encoder = TextEncoder::new();
39        let mut body = Vec::new();
40
41        if let Err(err) = encoder.encode(&metric_families, &mut body) {
42            let error = StatusCode::INTERNAL_SERVER_ERROR;
43            let text = err.to_string();
44
45            #[cfg(feature = "internal-logs")]
46            opentelemetry::otel_error!(name: "prometheus_encode_failure", error_code = error.as_u16(), error = text.clone());
47
48            Err((error, text).into_error())?;
49        }
50
51        let mut res = Response::new(Full::from(body).into());
52
53        res.headers_mut().append(
54            CONTENT_TYPE,
55            HeaderValue::from_str(encoder.format_type())
56                .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_error())?,
57        );
58
59        Ok(res)
60    }
61}