use actix_service::{forward_ready, Service, Transform}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::web::Data; use actix_web::Error; use futures_util::future::LocalBoxFuture; use std::future::{ready, Ready}; use std::sync::Arc; use super::request_id::get_request_id; use crate::api::metrics::RequestMetrics; pub struct MetricsMiddleware; impl Transform for MetricsMiddleware where S: Service, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type Transform = MetricsMiddlewareService; type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ready(Ok(MetricsMiddlewareService { service })) } } pub struct MetricsMiddlewareService { service: S, } impl Service for MetricsMiddlewareService where S: Service, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { // Get the metrics from the app_data let metrics = match req.app_data::>>() { Some(m) => m.clone(), None => { tracing::error!("Failed to get metrics from app_data"); return Box::pin(self.service.call(req)); }, }; let request_id = get_request_id(&req); let endpoint = req.match_pattern(); let method = req.method().to_string(); // Call the next service let res = self.service.call(req); Box::pin(async move { let start = std::time::Instant::now(); let res = res.await?; let end = std::time::Instant::now(); let duration = end.duration_since(start); let status = res.status(); if let Some(endpoint) = endpoint { metrics.record_request( request_id, endpoint, method, duration.as_millis() as u64, status.into(), ); } Ok(res) }) } }