use prometheus::{Counter, Histogram, Registry, Encoder, TextEncoder};
use std::sync::Arc;
use std::time::Instant;
use crate::{Handler, Request, Response, Middleware};
use async_trait::async_trait;
#[derive(Clone)]
pub struct Metrics {
pub request_counter: Counter,
pub request_duration: Histogram,
pub error_counter: Counter,
registry: Arc<Registry>,
}
impl Metrics {
pub fn new() -> Self {
let registry = Arc::new(Registry::new());
let request_counter = Counter::new("http_requests_total", "Total HTTP requests")
.expect("Failed to create request counter");
let request_duration = Histogram::new("http_request_duration_seconds", "HTTP request duration")
.expect("Failed to create request duration histogram");
let error_counter = Counter::new("http_errors_total", "Total HTTP errors")
.expect("Failed to create error counter");
registry.register(Box::new(request_counter.clone())).unwrap();
registry.register(Box::new(request_duration.clone())).unwrap();
registry.register(Box::new(error_counter.clone())).unwrap();
Metrics {
request_counter,
request_duration,
error_counter,
registry,
}
}
pub fn export(&self) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let encoder = TextEncoder::new();
let metric_families = self.registry.gather();
let mut buffer = Vec::new();
encoder.encode(&metric_families, &mut buffer)?;
Ok(String::from_utf8(buffer)?)
}
}
pub struct MetricsMiddleware {
metrics: Arc<Metrics>,
}
impl MetricsMiddleware {
pub fn new(metrics: Arc<Metrics>) -> Self {
MetricsMiddleware { metrics }
}
}
#[async_trait]
impl Middleware for MetricsMiddleware {
async fn handle(
&self,
req: Request,
next: Arc<dyn Handler>,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
let start = Instant::now();
self.metrics.request_counter.inc();
let result = next.handle(req).await;
let duration = start.elapsed();
self.metrics.request_duration.observe(duration.as_secs_f64());
if let Err(_) = &result {
self.metrics.error_counter.inc();
}
result
}
}