mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 09:10:32 +00:00
feat: add X-Request-ID middleware for request tracking
- Generate UUID v4 per request or accept upstream ID - Store in request extensions for handler access - Add to response headers for client correlation - Include in tracing spans for log correlation
This commit is contained in:
92
nora-registry/src/request_id.rs
Normal file
92
nora-registry/src/request_id.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
//! Request ID middleware for request tracking and correlation
|
||||
//!
|
||||
//! Generates a unique ID for each request that can be used for:
|
||||
//! - Log correlation across services
|
||||
//! - Debugging production issues
|
||||
//! - Client error reporting
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{header::HeaderName, HeaderValue, Request},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use tracing::{info_span, Instrument};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Header name for request ID
|
||||
pub static REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
|
||||
|
||||
/// Request ID wrapper type for extraction from request extensions
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RequestId(pub String);
|
||||
|
||||
impl std::ops::Deref for RequestId {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware that adds a unique request ID to each request.
|
||||
///
|
||||
/// The request ID is:
|
||||
/// 1. Taken from incoming `X-Request-ID` header if present (for upstream tracing)
|
||||
/// 2. Generated as a new UUID v4 if not present
|
||||
///
|
||||
/// The ID is:
|
||||
/// - Stored in request extensions for handlers to access
|
||||
/// - Added to the response `X-Request-ID` header
|
||||
/// - Included in the tracing span for log correlation
|
||||
pub async fn request_id_middleware(mut request: Request<Body>, next: Next) -> Response {
|
||||
// Check if request already has an ID (from upstream proxy/gateway)
|
||||
let request_id = request
|
||||
.headers()
|
||||
.get(&REQUEST_ID_HEADER)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
|
||||
// Store in request extensions for handlers to access
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(RequestId(request_id.clone()));
|
||||
|
||||
// Create tracing span with request metadata
|
||||
let span = info_span!(
|
||||
"request",
|
||||
request_id = %request_id,
|
||||
method = %request.method(),
|
||||
uri = %request.uri().path(),
|
||||
);
|
||||
|
||||
// Run the request handler within the span
|
||||
let mut response = next.run(request).instrument(span).await;
|
||||
|
||||
// Add request ID to response headers
|
||||
if let Ok(header_value) = HeaderValue::from_str(&request_id) {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(&REQUEST_ID_HEADER, header_value);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_request_id_deref() {
|
||||
let id = RequestId("test-123".to_string());
|
||||
assert_eq!(&*id, "test-123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_id_clone() {
|
||||
let id = RequestId("test-123".to_string());
|
||||
let cloned = id.clone();
|
||||
assert_eq!(id.0, cloned.0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user