1use log::trace;
2use prometheus::HistogramOpts;
3use prometheus::{HistogramVec, Registry};
4
5#[derive(Debug, Clone)]
6pub struct Metrics {
7 http_timer: HistogramVec,
8 include_path_labels: Vec<String>,
9}
10
11impl Metrics {
12 pub fn new(cr: &Registry, include_path_labels: &Vec<String>) -> Self {
13 let internal_http_timer_opts = HistogramOpts::new(
14 "server_response_duration_seconds",
15 "Route response time in seconds.",
16 );
17 let internal_http_timer =
18 HistogramVec::new(internal_http_timer_opts, &["classifier", "status"]).unwrap();
19 cr.register(Box::new(internal_http_timer.clone())).unwrap();
20
21 Self {
22 http_timer: internal_http_timer,
23 include_path_labels: include_path_labels.clone(),
24 }
25 }
26
27 fn sanitize_path_segments(&self, path: &str) -> String {
28 let path_segments: Vec<&str> = path.split('/').collect();
29 path_segments.iter().fold(String::new(), |acc, &path| {
30 if self.include_path_labels.contains(&path.to_string()) {
31 format!("{}/{}", acc, path)
32 } else if path == "" {
33 acc.to_string()
34 } else {
35 format!("{}/*", acc)
36 }
37 })
38 }
39
40 pub fn http_metrics(&self, info: warp::log::Info) {
64 trace!(
65 "Metric Status: {} - Method: {} - Path: {}",
66 &info.status().as_u16().to_string(),
67 &info.method(),
68 &info.path()
69 );
70 let sanitized_classifier = format!(
71 "{} - {}",
72 info.method(),
73 self.sanitize_path_segments(info.path())
74 );
75 self.http_timer
76 .with_label_values(&[&sanitized_classifier, info.status().as_str()])
77 .observe(info.elapsed().as_secs_f64());
78
79 }
80}
81
82#[cfg(test)]
83mod test {
84
85 use super::*;
86
87 #[test]
88 fn test_sanitize_path() {
89 let registry: Registry = Registry::new();
90 let path_includes: Vec<String> = vec![String::from("users"), String::from("registration")];
91
92 let metrics = Metrics::new(®istry, &path_includes);
93 let path = "/users/12345/registration/9797731279";
94 let sanitized_path = metrics.sanitize_path_segments(path);
95
96 assert_eq!("/users/*/registration/*".to_string(), sanitized_path)
97 }
98
99 #[test]
100 fn test_sanitize_path_with_value_first() {
101 let registry: Registry = Registry::new();
102 let path_includes: Vec<String> = vec![String::from("users"), String::from("registration")];
103
104 let metrics = Metrics::new(®istry, &path_includes);
105 let path = "12344235/users/12345/12314151252/registration";
106 let sanitized_path = metrics.sanitize_path_segments(path);
107
108 assert_eq!("/*/users/*/*/registration".to_string(), sanitized_path)
109 }
110
111 #[test]
112 fn test_sanitize_path_with_multiple_segments_in_order() {
113 let registry: Registry = Registry::new();
114 let path_includes: Vec<String> = vec![String::from("users"), String::from("registration")];
115
116 let metrics = Metrics::new(®istry, &path_includes);
117 let path = "/users/12345/12314151252/registration";
118 let sanitized_path = metrics.sanitize_path_segments(path);
119
120 assert_eq!("/users/*/*/registration".to_string(), sanitized_path)
121 }
122
123 #[test]
124 fn test_totally_wrong_path() {
125
126 let registry: Registry = Registry::new();
127 let path_includes: Vec<String> = vec![String::from("users"), String::from("registration")];
128
129 let metrics = Metrics::new(®istry, &path_includes);
130 let path = "12344235/12141242/12345/12314151252/235235235";
131 let sanitized_path = metrics.sanitize_path_segments(path);
132
133 assert_eq!("/*/*/*/*/*".to_string(), sanitized_path)
134 }
135}