SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
2.2 KB · 57 lines python
Raw Blame History
1"""Structured logging (JSON in production, human-readable in development)."""2from __future__ import annotations34import json5import logging6import sys7from datetime import UTC, datetime89from aiatlas.config import settings1011_RESERVED = {"name", "msg", "args", "levelname", "levelno", "pathname", "filename", "module", "exc_info", "exc_text", "stack_info",12             "lineno", "funcName", "created", "msecs", "relativeCreated", "thread", "threadName", "processName", "process",13             "message", "taskName"}141516class JsonFormatter(logging.Formatter):17    def __init__(self, service: str):18        super().__init__()19        self.service = service2021    def format(self, record: logging.LogRecord) -> str:22        payload = {23            "ts": datetime.now(UTC).isoformat(timespec="milliseconds"),24            "level": record.levelname,25            "logger": record.name,26            "service": self.service,27            "msg": record.getMessage(),28        }29        for key, value in record.__dict__.items():30            if key not in _RESERVED and not key.startswith("_"):31                payload[key] = value32        if record.exc_info:33            payload["exc"] = self.formatException(record.exc_info)34        return json.dumps(payload, default=str)353637class PlainFormatter(logging.Formatter):38    def format(self, record: logging.LogRecord) -> str:39        extras = {k: v for k, v in record.__dict__.items() if k not in _RESERVED and not k.startswith("_")}40        base = f"{datetime.now().strftime('%H:%M:%S')} {record.levelname:<7} {record.name}: {record.getMessage()}"41        if extras:42            base += " " + " ".join(f"{k}={v}" for k, v in extras.items())43        if record.exc_info:44            base += "\n" + self.formatException(record.exc_info)45        return base464748def setup_logging(level: int = logging.INFO, service: str = "aiatlas") -> None:49    root = logging.getLogger()50    root.handlers.clear()51    handler = logging.StreamHandler(sys.stderr)52    handler.setFormatter(JsonFormatter(service) if settings.log_json else PlainFormatter())53    root.addHandler(handler)54    root.setLevel(level)55    for noisy in ("httpx", "httpcore", "apscheduler", "asyncio", "urllib3"):56        logging.getLogger(noisy).setLevel(logging.WARNING)57