openzeppelin_relayer/metrics/
middleware.rs

1//! This defines the Middleware to collect metrics for the application.
2//! This middleware will increment the request counter for each request for each endpoint.
3
4use crate::metrics::{ERROR_COUNTER, RAW_REQUEST_COUNTER, REQUEST_COUNTER, REQUEST_LATENCY};
5use actix_web::{
6    dev::{Service, ServiceRequest, ServiceResponse, Transform},
7    Error,
8};
9use futures::future::{LocalBoxFuture, Ready};
10use std::{
11    task::{Context, Poll},
12    time::Instant,
13};
14
15pub struct MetricsMiddleware;
16
17/// Trait implementation for the MetricsMiddleware.
18impl<S, B> Transform<S, ServiceRequest> for MetricsMiddleware
19where
20    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
21    B: 'static,
22{
23    type Response = ServiceResponse<B>;
24    type Error = Error;
25    type InitError = ();
26    type Transform = MetricsMiddlewareService<S>;
27    type Future = Ready<Result<Self::Transform, Self::InitError>>;
28
29    fn new_transform(&self, service: S) -> Self::Future {
30        futures::future::ready(Ok(MetricsMiddlewareService { service }))
31    }
32}
33
34pub struct MetricsMiddlewareService<S> {
35    service: S,
36}
37
38/// Trait implementation for the MetricsMiddlewareService.
39impl<S, B> Service<ServiceRequest> for MetricsMiddlewareService<S>
40where
41    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
42    B: 'static,
43{
44    type Response = ServiceResponse<B>;
45    type Error = Error;
46    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
47
48    // Poll the service
49    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
50        self.service.poll_ready(cx)
51    }
52
53    // Call function to increment the request counter.
54    fn call(&self, req: ServiceRequest) -> Self::Future {
55        // Get the registered routes for the request.
56
57        // If not available, fall back to the raw path.
58        let endpoint = req
59            .match_pattern()
60            .unwrap_or_else(|| req.path().to_string());
61
62        // Get the HTTP method.
63        let method = req.method().to_string();
64
65        // Capture the raw URI.
66        let raw_uri = req.path().to_string();
67
68        // Start timer for latency.
69        let start_time = Instant::now();
70
71        let fut = self.service.call(req);
72        Box::pin(async move {
73            let res = fut.await;
74            // Compute elapsed time in seconds.
75            let elapsed = start_time.elapsed().as_secs_f64();
76
77            // Status code for success and error.
78            let status = match &res {
79                Ok(response) => response.response().status().to_string(),
80                Err(e) => e.as_response_error().status_code().to_string(),
81            };
82
83            // Add latency in histogram
84            REQUEST_LATENCY
85                .with_label_values(&[&endpoint, &method, &status])
86                .observe(elapsed);
87
88            match &res {
89                Ok(_) => {
90                    REQUEST_COUNTER
91                        .with_label_values(&[&endpoint, &method, &status])
92                        .inc();
93                }
94                Err(_) => {
95                    // Increment the error counter.
96                    ERROR_COUNTER
97                        .with_label_values(&[&endpoint, &method, &status])
98                        .inc();
99                }
100            }
101            // May be cardinality explosion here, but it's useful for debugging.
102            RAW_REQUEST_COUNTER
103                .with_label_values(&[&raw_uri, &method, &status])
104                .inc();
105            res
106        })
107    }
108}