use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use axum::{Router, http::StatusCode, routing::get};
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
use tracing::info;
pub struct MetricsExporter {
handle: PrometheusHandle,
ready: Arc<AtomicBool>,
}
impl MetricsExporter {
pub fn new(ready: Arc<AtomicBool>) -> Self {
let builder = PrometheusBuilder::new();
let handle = builder
.install_recorder()
.expect("failed to install metrics recorder");
Self { handle, ready }
}
pub async fn serve(
self,
addr: SocketAddr,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let handle = self.handle;
let ready = self.ready;
let app = Router::new()
.route(
"/metrics",
get(move || {
let h = handle.clone();
async move { h.render() }
}),
)
.route("/health", get(|| async { "ok" }))
.route(
"/ready",
get(move || {
let r = ready.clone();
async move {
if r.load(Ordering::Relaxed) {
(StatusCode::OK, "ready")
} else {
(StatusCode::SERVICE_UNAVAILABLE, "not ready")
}
}
}),
);
info!("Metrics server listening on {}", addr);
let listener = bind_tcp_reuseaddr(addr)?;
axum::serve(listener, app).await?;
Ok(())
}
}
fn bind_tcp_reuseaddr(addr: SocketAddr) -> std::io::Result<tokio::net::TcpListener> {
let socket = match addr {
SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
};
socket.set_reuseaddr(true)?;
socket.bind(addr)?;
socket.listen(1024)
}