| """Ingress-owned HTTP request correlation.""" |
|
|
| import uuid |
|
|
| from fastapi import Request, Response |
| from loguru import logger |
| from starlette.datastructures import Headers, MutableHeaders |
| from starlette.types import ASGIApp, Message, Receive, Scope, Send |
|
|
| from free_claude_code.core.trace import extract_claude_session_id_from_headers |
|
|
| REQUEST_ID_HEADER = "request-id" |
| OPENAI_REQUEST_ID_HEADER = "x-request-id" |
| _REQUEST_ID_STATE_ATTRIBUTE = "fcc_request_id" |
| _OPENAI_REQUEST_ID_PATHS = frozenset({"/v1/responses", "/v1/models"}) |
|
|
|
|
| class RequestCorrelationMiddleware: |
| """Own one request id and logging context for the full ASGI response.""" |
|
|
| def __init__(self, app: ASGIApp) -> None: |
| self._app = app |
|
|
| async def __call__( |
| self, |
| scope: Scope, |
| receive: Receive, |
| send: Send, |
| ) -> None: |
| if scope["type"] != "http": |
| await self._app(scope, receive, send) |
| return |
|
|
| request_id = new_request_id() |
| state = scope.setdefault("state", {}) |
| state[_REQUEST_ID_STATE_ATTRIBUTE] = request_id |
| method = scope.get("method", "") |
| path = scope.get("path", "") |
| request_headers = Headers(scope=scope) |
| claude_sid = extract_claude_session_id_from_headers(request_headers) |
|
|
| async def send_with_correlation(message: Message) -> None: |
| if message["type"] == "http.response.start": |
| message = dict(message) |
| raw_headers = list(message.get("headers", ())) |
| _set_request_id_headers( |
| MutableHeaders(raw=raw_headers), |
| request_id=request_id, |
| path=path, |
| ) |
| message["headers"] = raw_headers |
| await send(message) |
|
|
| with logger.contextualize( |
| http_method=method, |
| http_path=path, |
| claude_session_id=claude_sid, |
| request_id=request_id, |
| ): |
| await self._app(scope, receive, send_with_correlation) |
|
|
|
|
| def new_request_id() -> str: |
| """Return a new opaque FCC request identifier.""" |
| return f"req_{uuid.uuid4().hex}" |
|
|
|
|
| def set_request_id(request: Request, request_id: str) -> None: |
| """Attach the ingress correlation identifier to request state.""" |
| setattr(request.state, _REQUEST_ID_STATE_ATTRIBUTE, request_id) |
|
|
|
|
| def get_request_id(request: Request) -> str: |
| """Return the ingress correlation identifier, creating a fallback if needed.""" |
| request_id = getattr(request.state, _REQUEST_ID_STATE_ATTRIBUTE, None) |
| if isinstance(request_id, str) and request_id: |
| return request_id |
| request_id = new_request_id() |
| set_request_id(request, request_id) |
| return request_id |
|
|
|
|
| def attach_request_id_headers( |
| response: Response, *, request_id: str, path: str |
| ) -> None: |
| """Attach correlation when an outer server-error boundary bypasses middleware.""" |
| _set_request_id_headers(response.headers, request_id=request_id, path=path) |
|
|
|
|
| def _set_request_id_headers( |
| headers: MutableHeaders, |
| *, |
| request_id: str, |
| path: str, |
| ) -> None: |
| headers[REQUEST_ID_HEADER] = request_id |
| if path in _OPENAI_REQUEST_ID_PATHS: |
| headers[OPENAI_REQUEST_ID_HEADER] = request_id |
|
|