prometheus_serve_metrics/
lib.rs1use 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 let make_svc = make_service_fn(move |_conn| {
59 let state = state.clone();
60 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}