Skip to main content

minifly_api/middleware/
region.rs

1/// Region context middleware for adding region information to responses and logs
2/// 
3/// This middleware:
4/// - Adds region information to all API responses via headers
5/// - Injects region context into the logging framework
6/// - Tracks requests with correlation IDs for better debugging
7use axum::{
8    extract::Request,
9    http::{HeaderMap, HeaderValue},
10    middleware::Next,
11    response::Response,
12};
13use tracing::{info, instrument, Span};
14use uuid::Uuid;
15use minifly_logging::fields;
16
17/// Header name for region information
18pub const REGION_HEADER: &str = "x-minifly-region";
19
20/// Header name for correlation ID
21pub const CORRELATION_ID_HEADER: &str = "x-minifly-correlation-id";
22
23/// Default region for local development
24pub const DEFAULT_REGION: &str = "local";
25
26/// Middleware to add region context to requests and responses
27/// 
28/// This function:
29/// 1. Generates a unique correlation ID for each request
30/// 2. Adds region information to response headers
31/// 3. Injects structured logging with region and correlation context
32/// 4. Tracks request duration and outcomes
33#[instrument(
34    name = "region_middleware",
35    skip_all,
36    fields(
37        region = %DEFAULT_REGION,
38        correlation_id = tracing::field::Empty,
39        request_id = tracing::field::Empty,
40        http.method = %request.method(),
41        http.path = %request.uri().path(),
42        http.user_agent = tracing::field::Empty,
43        http.status = tracing::field::Empty,
44        duration_ms = tracing::field::Empty,
45    )
46)]
47pub async fn region_middleware(request: Request, next: Next) -> Response {
48    let correlation_id = minifly_logging::new_correlation_id();
49    let request_id = minifly_logging::new_request_id();
50    let region = DEFAULT_REGION.to_string();
51    
52    // Record structured fields in span
53    Span::current().record(fields::CORRELATION_ID, &correlation_id);
54    Span::current().record(fields::REQUEST_ID, &request_id);
55    Span::current().record(fields::REGION, &region);
56    
57    // Extract user agent if present
58    if let Some(user_agent) = request.headers().get("user-agent") {
59        if let Ok(ua_str) = user_agent.to_str() {
60            Span::current().record(fields::HTTP_USER_AGENT, ua_str);
61        }
62    }
63    
64    info!(
65        operation = "http_request_start",
66        "Processing HTTP request"
67    );
68    
69    let start_time = std::time::Instant::now();
70    
71    // Process the request
72    let mut response = next.run(request).await;
73    
74    let duration = start_time.elapsed();
75    
76    // Record final span fields
77    Span::current().record(fields::HTTP_STATUS, response.status().as_u16());
78    Span::current().record(fields::DURATION_MS, duration.as_millis());
79    
80    // Add region and correlation headers to response
81    let headers = response.headers_mut();
82    add_region_headers(headers, &region, &correlation_id);
83    
84    info!(
85        operation = "http_request_complete",
86        operation.status = "success",
87        "HTTP request completed successfully"
88    );
89    
90    response
91}
92
93/// Add region and correlation headers to the response
94/// 
95/// # Arguments
96/// * `headers` - Response headers to modify
97/// * `region` - Region identifier
98/// * `correlation_id` - Request correlation ID
99fn add_region_headers(headers: &mut HeaderMap, region: &str, correlation_id: &str) {
100    if let Ok(region_value) = HeaderValue::from_str(region) {
101        headers.insert(REGION_HEADER, region_value);
102    }
103    
104    if let Ok(correlation_value) = HeaderValue::from_str(correlation_id) {
105        headers.insert(CORRELATION_ID_HEADER, correlation_value);
106    }
107}
108
109/// Extract region from machine information for logging context
110/// 
111/// # Arguments
112/// * `machine_region` - Optional machine region, defaults to local
113/// 
114/// # Returns
115/// * Region string for logging and response headers
116pub fn get_machine_region(machine_region: Option<&str>) -> String {
117    machine_region.unwrap_or(DEFAULT_REGION).to_string()
118}
119
120/// Log machine operation with region context
121/// 
122/// # Arguments
123/// * `operation` - Operation being performed (e.g., "start", "stop", "create")
124/// * `machine_id` - Machine identifier
125/// * `app_name` - Application name
126/// * `region` - Region where operation is occurring
127#[instrument(skip_all)]
128pub fn log_machine_operation(operation: &str, machine_id: &str, app_name: &str, region: &str) {
129    info!(
130        operation = %operation,
131        machine_id = %machine_id,
132        app_name = %app_name,
133        region = %region,
134        "Machine operation"
135    );
136}
137
138/// Create structured log context for API operations
139/// 
140/// This macro helps create consistent logging across all API endpoints
141/// with region and correlation information.
142#[macro_export]
143macro_rules! api_log {
144    ($level:ident, $($field:ident = $value:expr),* $(,)? ; $($arg:tt)*) => {
145        tracing::$level!(
146            region = %crate::middleware::region::DEFAULT_REGION,
147            $($field = $value,)*
148            $($arg)*
149        )
150    };
151}