1use async_trait::async_trait;
2
3#[async_trait]
6pub trait Metrics: Send + Sync + 'static {
7 async fn incr_counter(&self, name: &str, value: u64);
9
10 async fn record_gauge(&self, name: &str, value: f64);
12
13 async fn start_timer(&self, name: &str) -> Option<Box<dyn Timer + Send>>;
16
17 async fn record_histogram(&self, name: &str, value: f64);
19
20 async fn record_histogram_with_tags(&self, name: &str, value: f64, tags: &[(&str, &str)]);
22
23 async fn incr_counter_with_tags(&self, name: &str, value: u64, tags: &[(&str, &str)]);
25
26 async fn record_gauge_with_tags(&self, name: &str, value: f64, tags: &[(&str, &str)]);
28
29 async fn record_error(&self, name: &str, error_type: &str);
31
32 async fn record_success(&self, name: &str, success: bool);
34}
35
36pub trait Timer: Send {
38 fn stop(self: Box<Self>);
40}
41
42pub struct NoopMetrics;
44
45#[async_trait]
46impl Metrics for NoopMetrics {
47 async fn incr_counter(&self, _name: &str, _value: u64) {}
48 async fn record_gauge(&self, _name: &str, _value: f64) {}
49 async fn start_timer(&self, _name: &str) -> Option<Box<dyn Timer + Send>> {
50 None
51 }
52 async fn record_histogram(&self, _name: &str, _value: f64) {}
53 async fn record_histogram_with_tags(&self, _name: &str, _value: f64, _tags: &[(&str, &str)]) {}
54 async fn incr_counter_with_tags(&self, _name: &str, _value: u64, _tags: &[(&str, &str)]) {}
55 async fn record_gauge_with_tags(&self, _name: &str, _value: f64, _tags: &[(&str, &str)]) {}
56 async fn record_error(&self, _name: &str, _error_type: &str) {}
57 async fn record_success(&self, _name: &str, _success: bool) {}
58}
59
60pub struct NoopTimer;
62impl Timer for NoopTimer {
63 fn stop(self: Box<Self>) {}
64}
65
66impl NoopMetrics {
67 pub fn new() -> Self {
68 NoopMetrics
69 }
70}
71
72impl Default for NoopMetrics {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[allow(async_fn_in_trait)]
80pub trait MetricsExt: Metrics {
81 async fn record_request(&self, name: &str, timer: Option<Box<dyn Timer + Send>>, success: bool) {
83 if let Some(t) = timer {
84 t.stop();
85 }
86 self.record_success(name, success).await;
87 }
88
89 async fn record_request_with_tags(
91 &self,
92 name: &str,
93 timer: Option<Box<dyn Timer + Send>>,
94 success: bool,
95 tags: &[(&str, &str)],
96 ) {
97 if let Some(t) = timer {
98 t.stop();
99 }
100 self.record_success(name, success).await;
101 self.incr_counter_with_tags(&format!("{}.total", name), 1, tags).await;
103 if success {
104 self.incr_counter_with_tags(&format!("{}.success", name), 1, tags).await;
105 } else {
106 self.incr_counter_with_tags(&format!("{}.failure", name), 1, tags).await;
107 }
108 }
109
110 async fn record_error_with_context(&self, name: &str, error_type: &str, context: &str) {
112 self.record_error(name, error_type).await;
113 self.incr_counter_with_tags(name, 1, &[("error_type", error_type), ("context", context)]).await;
114 }
115}
116
117impl<T: Metrics> MetricsExt for T {}