2026-04-27 00:50:12 +02:00
|
|
|
//! Custom [`MakeSpan`], [`OnResponse`], and [`MakeRequestId`] for request tracing.
|
2026-04-26 02:27:45 +02:00
|
|
|
//!
|
2026-04-27 00:50:12 +02:00
|
|
|
//! [`UuidRequestId`] — generates a UUID v4 per request for `SetRequestIdLayer`.
|
|
|
|
|
//!
|
|
|
|
|
//! [`ClientIpMakeSpan`] — records `request_id`, `client_ip`, `method`, `uri`,
|
|
|
|
|
//! and a placeholder `user_id` (filled by auth middleware) on every request span.
|
|
|
|
|
//!
|
|
|
|
|
//! [`LogBadRequest`] — emits a WARN for every HTTP 400 response, inheriting
|
|
|
|
|
//! all span fields so the log line includes request ID, IP, user, method, URI.
|
2026-04-26 02:27:45 +02:00
|
|
|
|
2026-04-27 00:50:12 +02:00
|
|
|
use axum::http::{HeaderValue, Request, Response, StatusCode};
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
use tower_http::request_id::{MakeRequestId, RequestId};
|
|
|
|
|
use tower_http::trace::{MakeSpan, OnResponse};
|
2026-04-26 02:27:45 +02:00
|
|
|
use tracing::Span;
|
2026-04-27 00:50:12 +02:00
|
|
|
use uuid::Uuid;
|
2026-04-26 02:27:45 +02:00
|
|
|
|
2026-04-27 00:50:12 +02:00
|
|
|
// ─── Request ID generator ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Generates a UUID v7 (fast, timed, sortable) for each request.
|
|
|
|
|
///
|
|
|
|
|
/// Used with [`tower_http::request_id::SetRequestIdLayer`]:
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// .layer(SetRequestIdLayer::x_request_id(UuidRequestId))
|
|
|
|
|
/// ```
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
pub struct UuidRequestId;
|
|
|
|
|
|
|
|
|
|
impl MakeRequestId for UuidRequestId {
|
|
|
|
|
fn make_request_id<B>(&mut self, _request: &Request<B>) -> Option<RequestId> {
|
|
|
|
|
let id = Uuid::now_v7().to_string();
|
|
|
|
|
HeaderValue::from_str(&id).ok().map(RequestId::new)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Span factory ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Implements [`MakeSpan`] so that every HTTP request span carries
|
|
|
|
|
/// `request_id`, `client_ip`, `method`, `uri`, and a deferred `user_id`.
|
|
|
|
|
///
|
|
|
|
|
/// `request_id` is read from the `x-request-id` header set by
|
|
|
|
|
/// [`tower_http::request_id::SetRequestIdLayer`] (which must wrap this layer).
|
2026-04-26 02:27:45 +02:00
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
pub struct ClientIpMakeSpan;
|
|
|
|
|
|
|
|
|
|
impl<B> MakeSpan<B> for ClientIpMakeSpan {
|
|
|
|
|
fn make_span(&mut self, request: &Request<B>) -> Span {
|
|
|
|
|
let ip = super::trusted_proxy::client_ip(request, true);
|
2026-04-27 00:50:12 +02:00
|
|
|
let request_id = request
|
|
|
|
|
.headers()
|
|
|
|
|
.get("x-request-id")
|
|
|
|
|
.and_then(|v| v.to_str().ok())
|
|
|
|
|
.unwrap_or("-");
|
2026-04-26 02:27:45 +02:00
|
|
|
tracing::info_span!(
|
|
|
|
|
"req",
|
2026-04-27 00:50:12 +02:00
|
|
|
request_id = request_id,
|
|
|
|
|
client_ip = %ip,
|
|
|
|
|
method = %request.method(),
|
|
|
|
|
uri = %request.uri().path(),
|
|
|
|
|
user_id = tracing::field::Empty,
|
2026-04-26 02:27:45 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-27 00:50:12 +02:00
|
|
|
|
|
|
|
|
// ─── Response observer ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Implements [`OnResponse`]: emits a WARN log for every HTTP 400 response.
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
pub struct LogBadRequest;
|
|
|
|
|
|
|
|
|
|
impl<B> OnResponse<B> for LogBadRequest {
|
|
|
|
|
fn on_response(self, response: &Response<B>, latency: Duration, _span: &Span) {
|
|
|
|
|
if response.status() == StatusCode::BAD_REQUEST {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
status = 400,
|
|
|
|
|
latency_ms = latency.as_millis(),
|
|
|
|
|
"bad request",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|