use axum::{
body::Body,
extract::{Request, State},
middleware::Next,
response::Response,
routing::get,
Router,
};
use metrics_exporter_prometheus::PrometheusBuilder;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{
app::{shutdown_signal, AppError, StatefulIocaine},
config::MetricsLabel,
};
pub async fn track_metrics(
State(iocaine): State<StatefulIocaine>,
req: Request,
next: Next,
) -> Result<Response<Body>, AppError> {
let headers = req.headers().clone();
let response = next.run(req).await;
let cfg = &iocaine.config.metrics;
let mut labels = Vec::new();
if cfg.labels.contains(&MetricsLabel::Host) {
if let Some(host) = headers.get("host") {
let host = host.to_str()?.to_string();
labels.push(("host", host));
}
}
if cfg.labels.contains(&MetricsLabel::UserAgent)
|| cfg.labels.contains(&MetricsLabel::UserAgentGroup)
{
if let Some(ua) = headers.get("user-agent") {
let user_agent = ua.to_str()?.to_string();
if cfg.labels.contains(&MetricsLabel::UserAgent) {
labels.push(("user-agent", user_agent.clone()));
}
if cfg.labels.contains(&MetricsLabel::UserAgentGroup) {
if let Some(group) = cfg
.agent_group
.iter()
.find(|agent_group_config| agent_group_config.agent.is_match(&user_agent))
{
labels.push(("user-agent-group", group.group.clone()));
}
}
}
}
metrics::counter!("iocaine_requests_total", &labels).increment(1);
Ok(response)
}
pub async fn start_metrics_server(metrics_bind: String) -> std::result::Result<(), AppError> {
let metrics_listener = tokio::net::TcpListener::bind(metrics_bind).await?;
let recorder_handle = PrometheusBuilder::new().install_recorder()?;
let app = Router::new().route("/metrics", get(|| async move { recorder_handle.render() }));
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let labels = [("service", "iocaine".to_string())];
metrics::gauge!("process_start_time_seconds", &labels).set(ts);
Ok(axum::serve(metrics_listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?)
}