"""Structured JSON logging and request context.""" from __future__ import annotations import contextvars import json import logging import sys from datetime import datetime, timezone from typing import Any request_id_context: contextvars.ContextVar[str] = contextvars.ContextVar( "request_id", default="-" ) class JsonFormatter(logging.Formatter): """Render log records as one-line JSON.""" _reserved = set(logging.makeLogRecord({}).__dict__) | {"message", "asctime"} def format(self, record: logging.LogRecord) -> str: payload: dict[str, Any] = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "request_id": getattr(record, "request_id", request_id_context.get()), } for key, value in record.__dict__.items(): if key not in self._reserved and not key.startswith("_"): payload[key] = value if record.exc_info: payload["exception"] = self.formatException(record.exc_info) return json.dumps(payload, default=str, ensure_ascii=False) def configure_logging(level: str = "INFO") -> None: """Configure process logging once.""" root = logging.getLogger() root.handlers.clear() handler = logging.StreamHandler(sys.stdout) handler.setFormatter(JsonFormatter()) root.addHandler(handler) root.setLevel(level.upper()) def get_logger(name: str) -> logging.Logger: return logging.getLogger(name)