diff --git a/crates/api/src/middleware.rs b/crates/api/src/middleware.rs index 382733f..bc5e756 100644 --- a/crates/api/src/middleware.rs +++ b/crates/api/src/middleware.rs @@ -1,4 +1,5 @@ // API key authentication middleware for admin routes. +// + Request ID middleware for trace propagation across services. use axum::{ body::Body, extract::State, @@ -11,6 +12,31 @@ use serde_json::json; use crate::state::AppState; + +// Request ID middleware: reads X-Request-Id header or generates a UUIDv4, +// injects it into response headers, and attaches it to the tracing span. +pub async fn request_id( + req: Request, + next: Next, +) -> Response { + let request_id = req + .headers() + .get("x-request-id") + .and_then(|v| v.to_str().ok()) + .map(str::to_string) + .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); + + tracing::Span::current().record("request_id", &request_id); + + let mut response = next.run(req).await; + response.headers_mut().insert( + "x-request-id", + request_id.parse().unwrap(), + ); + response +} + + pub async fn require_admin_key( State(state): State, req: Request, diff --git a/crates/gateway/src/routes.rs b/crates/gateway/src/routes.rs index 861394a..d67d749 100644 --- a/crates/gateway/src/routes.rs +++ b/crates/gateway/src/routes.rs @@ -20,11 +20,37 @@ use crate::attestation::{ use crate::state::AppState; use crate::webhook::{parse_merge_event, verify_github_signature}; +// Request ID middleware for trace propagation across services. +async fn request_id(req: Request, next: Next) -> Response { + use axum::body::Body; + use axum::http::Request; + use axum::middleware::Next; + use axum::response::Response; + + let request_id = req + .headers() + .get("x-request-id") + .and_then(|v| v.to_str().ok()) + .map(str::to_string) + .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); + + tracing::Span::current().record("request_id", &request_id); + + let mut response = next.run(req).await; + response.headers_mut().insert( + "x-request-id", + request_id.parse().unwrap(), + ); + response +} + + pub fn router(state: AppState) -> Router { Router::new() .route("/health", get(health)) .route("/ready", get(ready)) .route("/webhooks/github", post(github_webhook)) + .layer(axum::middleware::from_fn(request_id)) .with_state(state) }