konduto/sdk/metrics/
middleware.rs1use cadence::StatsdClient;
4use cadence::prelude::*;
5use http::Extensions;
6use reqwest::{Request, Response};
7use reqwest_middleware::{Middleware, Next, Result as MiddlewareResult};
8use std::sync::Arc;
9use std::time::Instant;
10
11pub struct StatsdMiddleware {
13 client: Arc<StatsdClient>,
14}
15
16impl StatsdMiddleware {
17 pub fn new(client: StatsdClient) -> Self {
19 Self {
20 client: Arc::new(client),
21 }
22 }
23
24 fn normalize_path(path: &str) -> String {
27 let segments: Vec<&str> = path.split('/').collect();
28 let normalized: Vec<String> = segments
29 .iter()
30 .map(|segment| {
31 if segment.is_empty() {
33 return String::from("");
34 }
35
36 if segment.starts_with('v') && segment.len() == 2 {
38 return segment.to_string();
39 }
40
41 match *segment {
43 "orders" | "blacklist" | "whitelist" | "greylist" | "email" | "ip"
44 | "tax_id" | "status" => segment.to_string(),
45 _ => ":id".to_string(),
47 }
48 })
49 .collect();
50
51 normalized.join("/")
52 }
53
54 fn latency_bucket(duration_ms: u64) -> &'static str {
56 match duration_ms {
57 0..=100 => "fast",
58 101..=500 => "medium",
59 501..=1000 => "slow",
60 _ => "very_slow",
61 }
62 }
63}
64
65#[async_trait::async_trait]
66impl Middleware for StatsdMiddleware {
67 async fn handle(
68 &self,
69 req: Request,
70 extensions: &mut Extensions,
71 next: Next<'_>,
72 ) -> MiddlewareResult<Response> {
73 let start = Instant::now();
74 let method = req.method().to_string();
75 let path = req.url().path().to_string();
76 let normalized_path = Self::normalize_path(&path);
77
78 let result = next.run(req, extensions).await;
80
81 let duration = start.elapsed();
83 let duration_ms = duration.as_millis() as u64;
84
85 match &result {
86 Ok(response) => {
87 let status = response.status().as_u16();
88 let status_class = format!("{}xx", status / 100);
89
90 let _ = self
93 .client
94 .count_with_tags("konduto.requests.hits", 1)
95 .with_tag("method", &method)
96 .with_tag("endpoint", &normalized_path)
97 .with_tag("status", &status.to_string())
98 .with_tag("status_class", &status_class)
99 .send();
100 println!("metrica requets.hts send");
101 let _ = self
103 .client
104 .time_with_tags("konduto.requests.latency", duration_ms)
105 .with_tag("method", &method)
106 .with_tag("endpoint", &normalized_path)
107 .with_tag("latency_bucket", Self::latency_bucket(duration_ms))
108 .send();
109
110 let _ = self
112 .client
113 .count_with_tags("konduto.requests.status", 1)
114 .with_tag("status", &status.to_string())
115 .with_tag("status_class", &status_class)
116 .send();
117 }
118 Err(_error) => {
119 let _ = self
121 .client
122 .count_with_tags("konduto.requests.errors", 1)
123 .with_tag("method", &method)
124 .with_tag("endpoint", &normalized_path)
125 .with_tag("error_type", "network_error")
126 .send();
127
128 let _ = self
130 .client
131 .time_with_tags("konduto.requests.latency", duration_ms)
132 .with_tag("method", &method)
133 .with_tag("endpoint", &normalized_path)
134 .with_tag("latency_bucket", Self::latency_bucket(duration_ms))
135 .with_tag("error", "true")
136 .send();
137 }
138 }
139
140 result
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_normalize_path_orders() {
150 assert_eq!(
151 StatsdMiddleware::normalize_path("/v1/orders/ORDER123"),
152 "/v1/orders/:id"
153 );
154 }
155
156 #[test]
157 fn test_normalize_path_decision_list() {
158 assert_eq!(
159 StatsdMiddleware::normalize_path("/v1/blacklist/email/test@example.com"),
160 "/v1/blacklist/email/:id"
161 );
162 }
163
164 #[test]
165 fn test_normalize_path_simple() {
166 assert_eq!(StatsdMiddleware::normalize_path("/v1/orders"), "/v1/orders");
167 }
168
169 #[test]
170 fn test_normalize_path_with_status() {
171 assert_eq!(
172 StatsdMiddleware::normalize_path("/v1/orders/ORDER123/status"),
173 "/v1/orders/:id/status"
174 );
175 }
176
177 #[test]
178 fn test_latency_bucket_fast() {
179 assert_eq!(StatsdMiddleware::latency_bucket(50), "fast");
180 assert_eq!(StatsdMiddleware::latency_bucket(100), "fast");
181 }
182
183 #[test]
184 fn test_latency_bucket_medium() {
185 assert_eq!(StatsdMiddleware::latency_bucket(101), "medium");
186 assert_eq!(StatsdMiddleware::latency_bucket(500), "medium");
187 }
188
189 #[test]
190 fn test_latency_bucket_slow() {
191 assert_eq!(StatsdMiddleware::latency_bucket(501), "slow");
192 assert_eq!(StatsdMiddleware::latency_bucket(1000), "slow");
193 }
194
195 #[test]
196 fn test_latency_bucket_very_slow() {
197 assert_eq!(StatsdMiddleware::latency_bucket(1001), "very_slow");
198 assert_eq!(StatsdMiddleware::latency_bucket(5000), "very_slow");
199 }
200}