rust_webx_host/
metrics.rs1use rust_webx_core::error::Result;
4use rust_webx_core::http::IHttpContext;
5use rust_webx_core::middleware::IMiddleware;
6use rust_webx_core::routing::IEndpoint;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9
10#[derive(Default)]
12pub struct HttpMetrics {
13 total: AtomicU64,
14 status_2xx: AtomicU64,
15 status_4xx: AtomicU64,
16 status_5xx: AtomicU64,
17}
18
19impl HttpMetrics {
20 pub fn new() -> Arc<Self> {
21 Arc::new(Self::default())
22 }
23
24 pub fn record(&self, status: u16) {
25 self.total.fetch_add(1, Ordering::Relaxed);
26 match status {
27 200..=299 => {
28 self.status_2xx.fetch_add(1, Ordering::Relaxed);
29 }
30 400..=499 => {
31 self.status_4xx.fetch_add(1, Ordering::Relaxed);
32 }
33 500..=599 => {
34 self.status_5xx.fetch_add(1, Ordering::Relaxed);
35 }
36 _ => {}
37 }
38 }
39
40 pub fn render_prometheus(&self) -> String {
41 let total = self.total.load(Ordering::Relaxed);
42 let s2 = self.status_2xx.load(Ordering::Relaxed);
43 let s4 = self.status_4xx.load(Ordering::Relaxed);
44 let s5 = self.status_5xx.load(Ordering::Relaxed);
45 format!(
46 "# HELP http_requests_total Total HTTP requests processed\n\
47 # TYPE http_requests_total counter\n\
48 http_requests_total {total}\n\
49 # HELP http_responses_2xx_total 2xx responses\n\
50 # TYPE http_responses_2xx_total counter\n\
51 http_responses_2xx_total {s2}\n\
52 # HELP http_responses_4xx_total 4xx responses\n\
53 # TYPE http_responses_4xx_total counter\n\
54 http_responses_4xx_total {s4}\n\
55 # HELP http_responses_5xx_total 5xx responses\n\
56 # TYPE http_responses_5xx_total counter\n\
57 http_responses_5xx_total {s5}\n"
58 )
59 }
60}
61
62pub struct MetricsMiddleware {
64 metrics: Arc<HttpMetrics>,
65}
66
67impl MetricsMiddleware {
68 pub fn new(metrics: Arc<HttpMetrics>) -> Self {
69 Self { metrics }
70 }
71}
72
73#[async_trait::async_trait]
74impl IMiddleware for MetricsMiddleware {
75 async fn invoke(
76 &self,
77 _ctx: &mut dyn IHttpContext,
78 ) -> Result<std::ops::ControlFlow<()>> {
79 Ok(std::ops::ControlFlow::Continue(()))
80 }
81
82 async fn after(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
83 self.metrics.record(ctx.response().status());
84 Ok(())
85 }
86}
87
88pub struct MetricsEndpoint {
90 metrics: Arc<HttpMetrics>,
91}
92
93impl MetricsEndpoint {
94 pub fn new(metrics: Arc<HttpMetrics>) -> Self {
95 Self { metrics }
96 }
97}
98
99#[async_trait::async_trait]
100impl IEndpoint for MetricsEndpoint {
101 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
102 let body = self.metrics.render_prometheus();
103 ctx.response_mut().set_status(200);
104 ctx.response_mut()
105 .set_header("content-type", "text/plain; version=0.0.4; charset=utf-8");
106 ctx.response_mut().write_bytes(body.into_bytes()).await
107 }
108}