mmap-worker / app /core /logging.py
jugalgajjar's picture
initial worker deploy
3b9a272
Raw
History Blame Contribute Delete
1.95 kB
"""JSON logging configured to carry per-request correlation IDs."""
from __future__ import annotations
import logging
from contextvars import ContextVar
from opentelemetry import trace
from pythonjsonlogger.json import JsonFormatter
request_id_var: ContextVar[str | None] = ContextVar("request_id", default=None)
user_id_var: ContextVar[str | None] = ContextVar("user_id", default=None)
class CorrelationFilter(logging.Filter):
"""Attach correlation IDs from contextvars and the current span onto records."""
def filter(self, record: logging.LogRecord) -> bool:
record.request_id = request_id_var.get()
record.user_id = user_id_var.get()
span = trace.get_current_span()
ctx = span.get_span_context() if span else None
if ctx and ctx.is_valid:
# Format ids as in the OTel spec — zero-padded lowercase hex.
record.trace_id = f"{ctx.trace_id:032x}"
record.span_id = f"{ctx.span_id:016x}"
else:
record.trace_id = None
record.span_id = None
return True
def configure_logging(level: str = "INFO") -> None:
"""Replace the root handler with a JSON one. Idempotent."""
formatter = JsonFormatter(
"{asctime} {levelname} {name} {message} {request_id} {user_id} {trace_id} {span_id}",
style="{",
rename_fields={"asctime": "ts", "levelname": "level", "name": "logger"},
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
handler.addFilter(CorrelationFilter())
root = logging.getLogger()
# Replace any handlers (uvicorn / pytest installs its own).
root.handlers = [handler]
root.setLevel(level.upper())
# uvicorn's named loggers shouldn't double-emit through their own handlers.
for name in ("uvicorn", "uvicorn.access", "uvicorn.error"):
logger = logging.getLogger(name)
logger.handlers = []
logger.propagate = True