Coverage for mcpgateway / middleware / correlation_id.py: 100%
28 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
1# -*- coding: utf-8 -*-
2"""Location: ./mcpgateway/middleware/correlation_id.py
3Copyright 2025
4SPDX-License-Identifier: Apache-2.0
5Authors: MCP Gateway Contributors
7Correlation ID (Request ID) Middleware.
9This middleware handles X-Correlation-ID HTTP headers and maps them to the internal
10request_id used throughout the system for unified request tracing.
12Key concept: HTTP X-Correlation-ID header → Internal request_id field (single ID for entire request flow)
14The middleware automatically extracts or generates request IDs for every HTTP request,
15stores them in context variables for async-safe propagation across services, and
16injects them back into response headers for client-side correlation.
18This enables end-to-end tracing: HTTP → Middleware → Services → Plugins → Logs (all with same request_id)
19"""
21# Standard
22import logging
23from typing import Callable
25# Third-Party
26from fastapi import Request, Response
27from starlette.middleware.base import BaseHTTPMiddleware
29# First-Party
30from mcpgateway.config import settings
31from mcpgateway.utils.correlation_id import (
32 clear_correlation_id,
33 extract_correlation_id_from_headers,
34 generate_correlation_id,
35 set_correlation_id,
36)
38logger = logging.getLogger(__name__)
41class CorrelationIDMiddleware(BaseHTTPMiddleware):
42 """Middleware for automatic request ID (correlation ID) handling.
44 This middleware:
45 1. Extracts request ID from X-Correlation-ID header in incoming requests
46 2. Generates a new UUID if no correlation ID is present
47 3. Stores the ID in context variables for the request lifecycle (used as request_id throughout system)
48 4. Injects the request ID into X-Correlation-ID response header
49 5. Cleans up context after request completion
51 The request ID extracted/generated here becomes the unified request_id used in:
52 - All log entries (request_id field)
53 - GlobalContext.request_id (when plugins execute)
54 - Service method calls for tracing
55 - Database queries for request tracking
57 Configuration is controlled via settings:
58 - correlation_id_enabled: Enable/disable the middleware
59 - correlation_id_header: Header name to use (default: X-Correlation-ID)
60 - correlation_id_preserve: Whether to preserve incoming IDs (default: True)
61 - correlation_id_response_header: Whether to add ID to responses (default: True)
62 """
64 def __init__(self, app):
65 """Initialize the correlation ID (request ID) middleware.
67 Args:
68 app: The FastAPI application instance
69 """
70 super().__init__(app)
71 self.header_name = getattr(settings, "correlation_id_header", "X-Correlation-ID")
72 self.preserve_incoming = getattr(settings, "correlation_id_preserve", True)
73 self.add_to_response = getattr(settings, "correlation_id_response_header", True)
75 async def dispatch(self, request: Request, call_next: Callable) -> Response:
76 """Process the request and manage request ID (correlation ID) lifecycle.
78 Extracts or generates a request ID, stores it in context variables for use throughout
79 the request lifecycle (becomes request_id in logs, services, plugins), and injects
80 it back into the X-Correlation-ID response header.
82 Args:
83 request: The incoming HTTP request
84 call_next: The next middleware or route handler
86 Returns:
87 Response: The HTTP response with correlation ID header added
88 """
89 # Extract correlation ID from incoming request headers
90 correlation_id = None
91 if self.preserve_incoming:
92 correlation_id = extract_correlation_id_from_headers(dict(request.headers), self.header_name)
94 # Generate new correlation ID if none was provided
95 if not correlation_id:
96 correlation_id = generate_correlation_id()
97 logger.debug(f"Generated new correlation ID: {correlation_id}")
98 else:
99 logger.debug(f"Using client-provided correlation ID: {correlation_id}")
101 # Store correlation ID in context variable for this request
102 # This makes it available to all downstream code (auth, services, plugins, logs)
103 set_correlation_id(correlation_id)
105 try:
106 # Process the request
107 response = await call_next(request)
109 # Add correlation ID to response headers if enabled
110 if self.add_to_response:
111 response.headers[self.header_name] = correlation_id
113 return response
115 finally:
116 # Clean up context after request completes
117 # Note: ContextVar automatically cleans up, but explicit cleanup is good practice
118 clear_correlation_id()