use std::time::Instant;
use crate::{
http::ResBody,
otel::{
otel_metrics::{Counter, Histogram},
semantic_conventions::trace,
*,
},
prelude::*,
};
pub struct Metrics {
request_count: Counter<u64>,
error_count: Counter<u64>,
duration: Histogram<f64>,
}
impl Metrics {
pub fn new(service_name: String) -> Self {
let meter = meter(service_name.clone());
Self {
request_count: meter
.u64_counter(service_name.clone() + "_request_count")
.with_description("total request count (since start of service)")
.init(),
error_count: meter
.u64_counter(service_name.clone() + "_error_count")
.with_description("failed request count (since start of service)")
.init(),
duration: meter
.f64_histogram(service_name.clone() + "_request_duration_ms")
.with_unit("milliseconds")
.with_description("request duration histogram (in milliseconds, since start of service)")
.init(),
}
}
}
#[async_trait]
impl Handler for Metrics {
async fn handle(&self, req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let mut labels = Vec::with_capacity(3);
labels.push(KeyValue::new(trace::HTTP_REQUEST_METHOD, req.method().to_string()));
labels.push(KeyValue::new(trace::URL_FULL, req.uri().to_string()));
let s = Instant::now();
ctrl.call_next(req, depot, res).await;
let elapsed = s.elapsed();
let status = res.status_code.unwrap_or(StatusCode::OK);
labels.push(KeyValue::new(trace::HTTP_RESPONSE_STATUS_CODE, status.as_u16() as i64));
if status.is_client_error() || status.is_server_error() {
self.error_count.add(1, &labels);
let msg = if let ResBody::Error(body) = &res.body {
body.to_string()
} else {
format!("ErrorCode: {}", status.as_u16())
};
labels.push(KeyValue::new(trace::EXCEPTION_MESSAGE, msg));
}
self.request_count.add(1, &labels);
self.duration.record(elapsed.as_secs_f64() * 1000.0_f64, &labels);
}
}