RAP_DocLayout_DetectionD / app /logging_config.py
RISHIVEL's picture
space deploy: fix zerogpu pickling
087643a
Raw History Blame Contribute Delete
2.12 kB
# Structured JSON logs, one line per request - lets me grep/jq for stuff
# later ("how many /ask requests hit the guardrail today") instead of
# re-running things to find out.
from __future__ import annotations
import json
import logging
import sys
import time
import uuid
from contextlib import contextmanager
class _JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
payload = {
"timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"message": record.getMessage(),
}
# extra={...} fields ride along here instead of needing a custom
# formatter per call site
for key, value in getattr(record, "extra_fields", {}).items():
payload[key] = value
return json.dumps(payload)
def configure_logging() -> logging.Logger:
logger = logging.getLogger("rap_doclayout")
if logger.handlers:
# avoid double handlers on repeated calls (app reload, test imports)
return logger
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(_JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
@contextmanager
def log_request(logger: logging.Logger, endpoint: str, **fields):
"""Logs one line per request with latency, success or failure either
way.
Usage:
with log_request(logger, "/detect") as ctx:
...
ctx["detection_count"] = len(detections)
"""
request_id = uuid.uuid4().hex[:12]
started = time.perf_counter()
ctx = {"request_id": request_id, "endpoint": endpoint, **fields}
error: Exception | None = None
try:
yield ctx
except Exception as exc:
error = exc
raise
finally:
ctx["latency_ms"] = round((time.perf_counter() - started) * 1000, 2)
if error is not None:
ctx["error"] = str(error)
logger.error("request failed", extra={"extra_fields": ctx})
else:
logger.info("request completed", extra={"extra_fields": ctx})