rust_webx_host/
request_tracing.rs1use 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};
8
9static COUNTER: AtomicU64 = AtomicU64::new(0);
10static TRACE_SEQ: 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 = ctx
36 .request()
37 .header("x-request-id")
38 .map(|s| s.to_string())
39 .unwrap_or_else(next_trace_id);
40 ctx.response_mut().set_header("x-trace-id", &tid);
41 Ok(ControlFlow::Continue(()))
42 }
43
44 async fn after(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
45 let status = ctx.response().status();
46 let is_err = status >= 400;
47
48 if self.log_all || is_err {
49 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
50 let method = ctx.request().method().to_string();
51 let path = ctx.request().path().to_string();
52 let user = ctx.claims().map(|c| c.subject().to_string());
53
54 if is_err {
55 tracing::warn!(
56 count = count, method = %method, path = %path,
57 status = status, user = %user.as_deref().unwrap_or("anon"),
58 "request error"
59 );
60 } else {
61 tracing::info!(
62 count = count, method = %method, path = %path,
63 status = status, user = %user.as_deref().unwrap_or("anon"),
64 "request ok"
65 );
66 }
67 }
68 Ok(())
69 }
70}
71
72fn next_trace_id() -> String {
73 let id = TRACE_SEQ.fetch_add(1, Ordering::Relaxed);
74 format!("{:016x}", id)
75}