""" Structured logging configuration for CBPOA backend. - LOG_LEVEL: DEBUG, INFO, WARNING, ERROR, CRITICAL (default INFO) - LOG_FORMAT: "json" for production, "text" for human-readable dev output (default text) """ import logging import json import sys import os from datetime import datetime, timezone class JSONFormatter(logging.Formatter): """Emit structured JSON log lines for production.""" def format(self, record: logging.LogRecord) -> str: log_entry = { "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), } if record.exc_info and record.exc_info[1]: log_entry["exception"] = self.formatException(record.exc_info) # Include extra fields (request_id, method, path, etc.) for key in ("request_id", "method", "path", "status_code", "duration_ms"): val = getattr(record, key, None) if val is not None: log_entry[key] = val return json.dumps(log_entry, ensure_ascii=False) def setup_logging() -> None: """Configure root logger based on environment variables.""" level_name = os.getenv("LOG_LEVEL", "INFO").upper() level = getattr(logging, level_name, logging.INFO) log_format = os.getenv("LOG_FORMAT", "text").lower() handler = logging.StreamHandler(sys.stdout) if log_format == "json": handler.setFormatter(JSONFormatter()) else: handler.setFormatter( logging.Formatter( "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) ) root = logging.getLogger() root.handlers.clear() root.addHandler(handler) root.setLevel(level) # Quiet noisy third-party loggers logging.getLogger("uvicorn.access").setLevel(logging.WARNING)