Skip to main content

rust_webx_host/
request_tracing.rs

1//! Structured request tracing middleware with trace_id injection.
2
3use rust_webx_core::error::Result;
4use rust_webx_core::http::IHttpContext;
5use rust_webx_core::middleware::IMiddleware;
6use std::ops::ControlFlow;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{LazyLock, Mutex};
9
10static COUNTER: AtomicU64 = AtomicU64::new(0);
11
12pub struct RequestTracing {
13    pub log_all: bool,
14}
15
16impl RequestTracing {
17    pub fn new() -> Self {
18        Self { log_all: true }
19    }
20    pub fn errors_only() -> Self {
21        Self { log_all: false }
22    }
23}
24
25impl Default for RequestTracing {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31#[async_trait::async_trait]
32impl IMiddleware for RequestTracing {
33    async fn invoke(&self, ctx: &mut dyn IHttpContext) -> Result<ControlFlow<()>> {
34        let tid = next_trace_id();
35        ctx.response_mut().set_header("x-trace-id", &tid);
36        Ok(ControlFlow::Continue(()))
37    }
38
39    async fn after(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
40        let status = ctx.response().status();
41        let is_err = status >= 400;
42
43        if self.log_all || is_err {
44            let count = COUNTER.fetch_add(1, Ordering::Relaxed);
45            let method = ctx.request().method().to_string();
46            let path = ctx.request().path().to_string();
47            let user = ctx.claims().map(|c| c.subject().to_string());
48
49            if is_err {
50                tracing::warn!(
51                    count = count, method = %method, path = %path,
52                    status = status, user = %user.as_deref().unwrap_or("anon"),
53                    "request error"
54                );
55            } else {
56                tracing::info!(
57                    count = count, method = %method, path = %path,
58                    status = status, user = %user.as_deref().unwrap_or("anon"),
59                    "request ok"
60                );
61            }
62        }
63        Ok(())
64    }
65}
66
67struct XorShift(std::sync::Mutex<u64>);
68static XORSHIFT: LazyLock<XorShift> = LazyLock::new(|| {
69    XorShift(Mutex::new(
70        std::time::SystemTime::now()
71            .duration_since(std::time::UNIX_EPOCH)
72            .map(|d| d.as_nanos() as u64)
73            .unwrap_or(1),
74    ))
75});
76
77
78static TRACE_BUF: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(String::with_capacity(16)));
79
80fn next_trace_id() -> String {
81    let id = {
82        let mut x = match XORSHIFT.0.lock() {
83            Ok(g) => g,
84            Err(_) => return format!("{:016x}", fast_rand()),
85        };
86        *x ^= *x << 13;
87        *x ^= *x >> 7;
88        *x ^= *x << 17;
89        *x
90    };
91    let mut buf = match TRACE_BUF.lock() {
92        Ok(g) => g,
93        Err(_) => return format!("{:016x}", id),
94    };
95    buf.clear();
96    use std::fmt::Write;
97    let _ = write!(&mut *buf, "{:016x}", id);
98    buf.clone()
99}
100
101fn fast_rand() -> u64 {
102    let t = std::time::SystemTime::now()
103        .duration_since(std::time::UNIX_EPOCH)
104        .map(|d| d.as_nanos() as u64)
105        .unwrap_or(42);
106    t.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407)
107}