Skip to main content

zebra_rpc/server/
rpc_metrics.rs

1//! RPC metrics middleware for Prometheus metrics collection.
2//!
3//! This middleware collects metrics for JSON-RPC requests, including:
4//! - Request count by method and status
5//! - Request duration by method
6//! - Active request count
7//! - Error count by method and error code
8//!
9//! These metrics complement the OpenTelemetry tracing in `rpc_tracing.rs`,
10//! providing aggregated data suitable for dashboards and alerting.
11
12use std::time::Instant;
13
14use jsonrpsee::{
15    server::middleware::rpc::{layer::ResponseFuture, RpcServiceT},
16    MethodResponse,
17};
18
19/// Middleware that collects Prometheus metrics for each RPC request.
20///
21/// This middleware records:
22/// - `rpc.requests.total{method, status}` - Counter of requests by method and status
23/// - `rpc.request.duration_seconds{method}` - Histogram of request durations
24/// - `rpc.active_requests` - Gauge of currently active requests
25/// - `rpc.errors.total{method, error_code}` - Counter of errors by method and code
26#[derive(Clone)]
27pub struct RpcMetricsMiddleware<S> {
28    service: S,
29}
30
31impl<S> RpcMetricsMiddleware<S> {
32    /// Create a new `RpcMetricsMiddleware` with the given `service`.
33    pub fn new(service: S) -> Self {
34        Self { service }
35    }
36}
37
38impl<'a, S> RpcServiceT<'a> for RpcMetricsMiddleware<S>
39where
40    S: RpcServiceT<'a> + Send + Sync + Clone + 'static,
41{
42    type Future = ResponseFuture<futures::future::BoxFuture<'a, MethodResponse>>;
43
44    fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future {
45        let service = self.service.clone();
46        let method = request.method_name().to_owned();
47        let start = Instant::now();
48
49        // Increment active requests gauge
50        metrics::gauge!("rpc.active_requests").increment(1.0);
51
52        ResponseFuture::future(Box::pin(async move {
53            let response = service.call(request).await;
54            let duration = start.elapsed().as_secs_f64();
55
56            // Determine status and record metrics
57            let status = if response.is_error() {
58                "error"
59            } else {
60                "success"
61            };
62
63            // Record request count
64            metrics::counter!("rpc.requests.total", "method" => method.clone(), "status" => status)
65                .increment(1);
66
67            // Record request duration
68            metrics::histogram!("rpc.request.duration_seconds", "method" => method.clone())
69                .record(duration);
70
71            // Record errors with error code
72            if response.is_error() {
73                let error_code = response
74                    .as_error_code()
75                    .map(|c| c.to_string())
76                    .unwrap_or_else(|| "unknown".to_string());
77                metrics::counter!(
78                    "rpc.errors.total",
79                    "method" => method,
80                    "error_code" => error_code
81                )
82                .increment(1);
83            }
84
85            // Decrement active requests gauge
86            metrics::gauge!("rpc.active_requests").decrement(1.0);
87
88            response
89        }))
90    }
91}