feat(ocr): suppress uvicorn access logs for /metrics and /health
Adds a logging.Filter on uvicorn.access that drops records whose request path is /metrics or /health. Each is hit on a tight schedule (Prometheus scrape interval and Docker healthcheck), so unfiltered they dominate the access log without carrying any information about real traffic. Refs #652 (Nora's recommendation) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,23 @@ app = FastAPI(title="Familienarchiv OCR Service", lifespan=lifespan)
|
||||
Instrumentator(excluded_handlers=["/health", "/metrics"]).instrument(app).expose(app)
|
||||
|
||||
|
||||
class MetricsPathFilter(logging.Filter):
|
||||
"""Drop uvicorn.access entries for /metrics and /health to keep logs focused."""
|
||||
|
||||
_SUPPRESSED_PATHS = {"/metrics", "/health"}
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
# uvicorn.access formats as: '%s - "%s %s HTTP/%s" %d'
|
||||
if record.args and len(record.args) >= 3:
|
||||
path = record.args[2]
|
||||
if isinstance(path, str) and path in self._SUPPRESSED_PATHS:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
logging.getLogger("uvicorn.access").addFilter(MetricsPathFilter())
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
"""Health endpoint — returns 200 only after models are loaded."""
|
||||
|
||||
@@ -442,3 +442,33 @@ async def test_ocr_models_ready_gauge_is_one_after_lifespan_startup(fresh_metric
|
||||
patch("main.load_spell_checker"):
|
||||
async with app.router.lifespan_context(app):
|
||||
assert fresh_metrics.ocr_models_ready._value.get() == 1.0
|
||||
|
||||
|
||||
def test_uvicorn_access_log_filter_skips_metrics_path():
|
||||
"""The MetricsPathFilter drops uvicorn.access log records that target /metrics."""
|
||||
import logging as _logging
|
||||
from main import MetricsPathFilter
|
||||
|
||||
filt = MetricsPathFilter()
|
||||
metrics_record = _logging.LogRecord(
|
||||
name="uvicorn.access", level=_logging.INFO, pathname="", lineno=0,
|
||||
msg='%s - "%s %s HTTP/%s" %d',
|
||||
args=("127.0.0.1:1234", "GET", "/metrics", "1.1", 200),
|
||||
exc_info=None,
|
||||
)
|
||||
health_record = _logging.LogRecord(
|
||||
name="uvicorn.access", level=_logging.INFO, pathname="", lineno=0,
|
||||
msg='%s - "%s %s HTTP/%s" %d',
|
||||
args=("127.0.0.1:1234", "GET", "/health", "1.1", 200),
|
||||
exc_info=None,
|
||||
)
|
||||
ocr_record = _logging.LogRecord(
|
||||
name="uvicorn.access", level=_logging.INFO, pathname="", lineno=0,
|
||||
msg='%s - "%s %s HTTP/%s" %d',
|
||||
args=("127.0.0.1:1234", "POST", "/ocr", "1.1", 200),
|
||||
exc_info=None,
|
||||
)
|
||||
|
||||
assert filt.filter(metrics_record) is False
|
||||
assert filt.filter(health_record) is False
|
||||
assert filt.filter(ocr_record) is True
|
||||
|
||||
Reference in New Issue
Block a user