This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/middleware/request_id.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

139 lines
3.6 KiB
Python

"""
Request-ID Middleware
Generates and propagates unique request identifiers for distributed tracing.
Supports both X-Request-ID and X-Correlation-ID headers.
Usage:
from middleware import RequestIDMiddleware, get_request_id
app.add_middleware(RequestIDMiddleware)
@app.get("/api/example")
async def example():
request_id = get_request_id()
logger.info(f"Processing request", extra={"request_id": request_id})
"""
import uuid
from contextvars import ContextVar
from typing import Optional
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
# Context variable to store request ID across async calls
_request_id_ctx: ContextVar[Optional[str]] = ContextVar("request_id", default=None)
# Header names
REQUEST_ID_HEADER = "X-Request-ID"
CORRELATION_ID_HEADER = "X-Correlation-ID"
def get_request_id() -> Optional[str]:
"""
Get the current request ID from context.
Returns:
The request ID string or None if not in a request context.
Example:
request_id = get_request_id()
logger.info("Processing", extra={"request_id": request_id})
"""
return _request_id_ctx.get()
def set_request_id(request_id: str) -> None:
"""
Set the request ID in the current context.
Args:
request_id: The request ID to set
"""
_request_id_ctx.set(request_id)
def generate_request_id() -> str:
"""
Generate a new unique request ID.
Returns:
A UUID4 string
"""
return str(uuid.uuid4())
class RequestIDMiddleware(BaseHTTPMiddleware):
"""
Middleware that generates and propagates request IDs.
For each incoming request:
1. Check for existing X-Request-ID or X-Correlation-ID header
2. If not present, generate a new UUID
3. Store in context for use by handlers and logging
4. Add to response headers
Attributes:
header_name: The primary header name to use (default: X-Request-ID)
generator: Function to generate new IDs (default: uuid4)
"""
def __init__(
self,
app,
header_name: str = REQUEST_ID_HEADER,
generator=generate_request_id,
):
super().__init__(app)
self.header_name = header_name
self.generator = generator
async def dispatch(self, request: Request, call_next) -> Response:
# Try to get existing request ID from headers
request_id = (
request.headers.get(REQUEST_ID_HEADER)
or request.headers.get(CORRELATION_ID_HEADER)
)
# Generate new ID if not provided
if not request_id:
request_id = self.generator()
# Store in context for logging and handlers
set_request_id(request_id)
# Store in request state for direct access
request.state.request_id = request_id
# Process request
response = await call_next(request)
# Add request ID to response headers
response.headers[REQUEST_ID_HEADER] = request_id
response.headers[CORRELATION_ID_HEADER] = request_id
return response
class RequestIDLogFilter:
"""
Logging filter that adds request_id to log records.
Usage:
import logging
handler = logging.StreamHandler()
handler.addFilter(RequestIDLogFilter())
formatter = logging.Formatter(
'%(asctime)s [%(request_id)s] %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
"""
def filter(self, record):
record.request_id = get_request_id() or "no-request-id"
return True