use axum::{
extract::Request,
http::{header::HeaderName, HeaderValue},
middleware::Next,
response::Response,
};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
const REQUEST_ID_HEADER: &str = "x-request-id";
const HEALTH_PATH: &str = "/api/v1/health";
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
pub async fn logger(req: Request, next: Next) -> Response {
let inbound_id = req
.headers()
.get(REQUEST_ID_HEADER)
.and_then(|header| header.to_str().ok())
.filter(|header| !header.is_empty())
.map(str::to_string);
let id = inbound_id
.unwrap_or_else(|| format!("{:08x}", REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed)));
let method = req.method().clone();
let path = req.uri().path().to_string();
let level = if path == HEALTH_PATH {
log::Level::Debug
} else {
log::Level::Info
};
log::log!(level, "[{id}] <- {method} {path}");
let start = Instant::now();
let mut res = next.run(req).await;
let status = res.status();
let elapsed = start.elapsed().as_millis();
log::log!(level, "[{id}] -> {status} {path} in {elapsed}ms");
if let Ok(value) = HeaderValue::from_str(&id) {
res.headers_mut()
.insert(HeaderName::from_static(REQUEST_ID_HEADER), value);
}
res
}
#[cfg(test)]
#[path = "logger_tests.rs"]
mod logger_tests;