prometheus_serve_metrics/
lib.rs

1use anyhow::{anyhow, Result};
2use hyper::{
3    header::CONTENT_TYPE,
4    service::{make_service_fn, service_fn},
5};
6use hyper::{Body, Method, Request, Response, Server};
7use opentelemetry_prometheus::PrometheusExporter;
8use prometheus::{Encoder, TextEncoder};
9use std::{convert::Infallible, net::SocketAddr, sync::Arc};
10use tokio::task::JoinHandle;
11use tracing::info;
12
13async fn metrics(req: Request<Body>, state: Arc<AppState>) -> Result<Response<Body>, hyper::Error> {
14    let response = match (req.method(), req.uri().path()) {
15        (&Method::GET, "/metrics") => {
16            let mut buffer = vec![];
17            let encoder = TextEncoder::new();
18            let metric_families = state.exporter.registry().gather();
19            encoder.encode(&metric_families, &mut buffer).unwrap();
20
21            Response::builder()
22                .status(200)
23                .header(CONTENT_TYPE, encoder.format_type())
24                .body(Body::from(buffer))
25                .unwrap()
26        }
27        _ => Response::builder()
28            .status(404)
29            .body(Body::from("Not Found"))
30            .unwrap(),
31    };
32
33    Ok(response)
34}
35
36struct AppState {
37    exporter: PrometheusExporter,
38}
39
40pub fn start_metrics_server() -> Result<(SocketAddr, JoinHandle<Result<()>>)> {
41    let addr = ([0, 0, 0, 0], 9090).into();
42
43    let handle = tokio::spawn(async move {
44        let exporter = match opentelemetry_prometheus::exporter().try_init() {
45            Ok(exporter) => exporter,
46            Err(err) => {
47                return Err(anyhow!(
48                    "Failed to creat prometheus serve metrics {:?}",
49                    err
50                ))
51            }
52        };
53
54        let state = Arc::new(AppState { exporter });
55
56        // For every connection, we must make a `Service` to handle all
57        // incoming HTTP requests on said connection.
58        let make_svc = make_service_fn(move |_conn| {
59            let state = state.clone();
60            // This is the `Service` that will handle the connection.
61            // `service_fn` is a helper to convert a function that
62            // returns a Response into a `Service`.
63            async move { Ok::<_, Infallible>(service_fn(move |req| metrics(req, state.clone()))) }
64        });
65
66        let server = Server::bind(&addr).serve(make_svc);
67
68        info!("Serving prometheus metrics on http://{}", addr);
69
70        server.await.map_err(Into::into)
71    });
72
73    Ok((addr, handle))
74}