1use std::time::Instant;
2
3use opentelemetry::metrics::{Counter, Histogram};
4use opentelemetry::{KeyValue, global};
5use opentelemetry_semantic_conventions::trace;
6use salvo_core::http::ResBody;
7use salvo_core::prelude::*;
8
9pub struct Metrics {
11 request_count: Counter<u64>,
12 error_count: Counter<u64>,
13 duration: Histogram<f64>,
14}
15
16impl Default for Metrics {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl Metrics {
23 pub fn new() -> Self {
25 let meter = global::meter("salvo");
26 Self {
27 request_count: meter
28 .u64_counter("salvo_request_count")
29 .with_description("total request count (since start of service)")
30 .build(),
31 error_count: meter
32 .u64_counter("salvo_error_count")
33 .with_description("failed request count (since start of service)")
34 .build(),
35 duration: meter
36 .f64_histogram("salvo_request_duration_ms")
37 .with_unit("milliseconds")
38 .with_description(
39 "request duration histogram (in milliseconds, since start of service)",
40 )
41 .build(),
42 }
43 }
44}
45
46#[async_trait]
47impl Handler for Metrics {
48 async fn handle(
49 &self,
50 req: &mut Request,
51 depot: &mut Depot,
52 res: &mut Response,
53 ctrl: &mut FlowCtrl,
54 ) {
55 let mut labels = Vec::with_capacity(3);
56 labels.push(KeyValue::new(
57 trace::HTTP_REQUEST_METHOD,
58 req.method().to_string(),
59 ));
60 labels.push(KeyValue::new(trace::URL_FULL, req.uri().to_string()));
61
62 let s = Instant::now();
63 ctrl.call_next(req, depot, res).await;
64 let elapsed = s.elapsed();
65
66 let status = res.status_code.unwrap_or_else(|| {
67 tracing::info!("[otel::Metrics] Treat status_code=none as 200(OK).");
68 StatusCode::OK
69 });
70 labels.push(KeyValue::new(
71 trace::HTTP_RESPONSE_STATUS_CODE,
72 status.as_u16() as i64,
73 ));
74 if status.is_client_error() || status.is_server_error() {
75 self.error_count.add(1, &labels);
76 let msg = if let ResBody::Error(body) = &res.body {
77 body.to_string()
78 } else {
79 format!("ErrorCode: {}", status.as_u16())
80 };
81 labels.push(KeyValue::new(trace::EXCEPTION_MESSAGE, msg));
82 }
83
84 self.request_count.add(1, &labels);
85 self.duration
86 .record(elapsed.as_secs_f64() * 1000.0, &labels);
87 }
88}