add X-Request-Id for each req, log all 400 errors
This commit is contained in:
@@ -1,25 +1,81 @@
|
||||
//! Custom [`MakeSpan`] that records the client IP in every request span.
|
||||
//! Custom [`MakeSpan`], [`OnResponse`], and [`MakeRequestId`] for request tracing.
|
||||
//!
|
||||
//! IP resolution delegates to [`super::trusted_proxy::client_ip`]:
|
||||
//! 1. TCP peer in `OXICLOUD_TRUST_PROXY_CIDR` → `X-Forwarded-For` / `X-Real-Ip`
|
||||
//! 2. Otherwise → raw TCP peer address (with port, e.g. `127.0.0.1:12345`)
|
||||
//! [`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.
|
||||
|
||||
use axum::http::Request;
|
||||
use tower_http::trace::MakeSpan;
|
||||
use axum::http::{HeaderValue, Request, Response, StatusCode};
|
||||
use std::time::Duration;
|
||||
use tower_http::request_id::{MakeRequestId, RequestId};
|
||||
use tower_http::trace::{MakeSpan, OnResponse};
|
||||
use tracing::Span;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Implements [`MakeSpan`] so that every HTTP request span carries a
|
||||
/// `client_ip` field visible in every log line produced inside that span.
|
||||
// ─── 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).
|
||||
#[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);
|
||||
let request_id = request
|
||||
.headers()
|
||||
.get("x-request-id")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("-");
|
||||
tracing::info_span!(
|
||||
"req",
|
||||
client_ip = %ip,
|
||||
user_id = tracing::field::Empty,
|
||||
request_id = request_id,
|
||||
client_ip = %ip,
|
||||
method = %request.method(),
|
||||
uri = %request.uri().path(),
|
||||
user_id = tracing::field::Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user