merge: align dev branch with main

This commit is contained in:
Developer
2026-06-03 08:51:02 +00:00
parent 51a399c775
commit b39d6ce5f4
319 changed files with 30221 additions and 9221 deletions
+8 -41
View File
@@ -1,52 +1,15 @@
"""Structured JSON logging configuration."""
import json
import logging
import sys
import time
import traceback
from collections.abc import Callable
from typing import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from src.services.correlation import get_correlation_id
logger = logging.getLogger(__name__)
class CorrelationIdFilter(logging.Filter):
"""Inject correlation_id into every log record from context var."""
def filter(self, record: logging.LogRecord) -> bool:
record.correlation_id = get_correlation_id() # type: ignore[attr-defined]
return True
class JSONFormatter(logging.Formatter):
"""Emit log records as single-line JSON."""
def format(self, record: logging.LogRecord) -> str:
log_obj: dict = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"correlation_id": getattr(record, "correlation_id", None),
}
# Optional extra fields
for key in ("instance_id", "event_type"):
value = getattr(record, key, None)
if value is not None:
log_obj[key] = value
if record.exc_info:
log_obj["exception"] = self.formatException(record.exc_info)
return json.dumps(log_obj, default=str)
def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
return time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(record.created))
class RequestLoggingMiddleware(BaseHTTPMiddleware):
"""Log all HTTP requests with timing and status codes."""
@@ -54,6 +17,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
start_time = time.time()
client_host = request.client.host if request.client else "unknown"
# Log the incoming request
logger.info(
"→ Request: %s %s (client: %s)",
request.method,
@@ -65,6 +29,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
response = await call_next(request)
duration = time.time() - start_time
# Log the response
logger.info(
"← Response: %s %s%d (%dms)",
request.method,
@@ -104,13 +69,15 @@ class ExceptionLoggingMiddleware(BaseHTTPMiddleware):
def configure_logging(level: int = logging.INFO) -> None:
"""Configure structured JSON logging for the application."""
formatter = JSONFormatter()
"""Configure structured logging for the application."""
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
console_handler.addFilter(CorrelationIdFilter())
# Configure root logger
root_logger = logging.getLogger()