TextPhantom-v4.0.0 / backend /api /middleware.py
plan291037's picture
Add files via upload
121f213
Raw
History Blame Contribute Delete
3.09 kB
"""Quiet HTTP/WebSocket logging for production.
The stock uvicorn access log is too noisy for this app because extensions poll
``/health``, ``/warmup``, ``/meta`` and ``/translate/{id}`` frequently. By
default we disable uvicorn access logs and only emit compact application events
from the route/job code.
Modes via ``TP_ACCESS_LOG_MODE``:
- ``summary`` (default): one compact line for important success/error outcomes.
- ``off`` / ``none``: no app summary lines.
- ``uvicorn``: restore uvicorn's stock access log.
"""
from __future__ import annotations
import logging
from http import HTTPStatus
from fastapi import Request
from backend.config import settings
from backend.log import event
_UVICORN_MODE = "uvicorn"
_EVENT_MODES = {"summary", "custom", "tp", "plain"}
_NOISY_PATHS = ("/health", "/warmup", "/meta")
def _quiet_logger(name: str, *, disable: bool = False) -> None:
"""Lower a third-party logger without risking request handling."""
try:
logger = logging.getLogger(name)
if disable:
logger.disabled = True
logger.propagate = False
logger.setLevel(logging.CRITICAL)
else:
logger.setLevel(logging.WARNING)
except Exception:
pass
def configure_uvicorn_access_log() -> None:
"""Silence uvicorn/websocket request chatter unless explicitly restored."""
if settings.access_log_mode == _UVICORN_MODE:
return
# This is the source of lines like:
# INFO: 10.x.x.x:12345 - "GET /health HTTP/1.1" 200 OK
_quiet_logger("uvicorn.access", disable=True)
# These suppress websocket lifecycle chatter such as "connection open" /
# "connection closed" while preserving warnings and errors.
_quiet_logger("websockets.server")
_quiet_logger("websockets.protocol")
async def access_log_middleware(request: Request, call_next):
"""Log only HTTP failures; success summaries are emitted by route/job code."""
try:
response = await call_next(request)
except Exception as exc:
if settings.access_log_mode in _EVENT_MODES:
event(
"http.error",
{
"method": request.method,
"path": request.url.path,
"error": str(exc)[:240],
},
ok=False,
)
raise
if settings.access_log_mode in _EVENT_MODES:
try:
path = request.url.path
# No health/warmup/meta/poll success spam. Only surface HTTP errors.
if response.status_code >= 400 and not path.startswith(_NOISY_PATHS):
phrase = HTTPStatus(response.status_code).phrase
event(
"http.error",
{
"method": request.method,
"path": path,
"status": response.status_code,
"message": phrase,
},
ok=False,
)
except Exception:
pass
return response