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}