"""Кольцевой буфер последних записей журнала. Нужен, чтобы смотреть логи через API, а не копировать их из окна консоли. Хранится в памяти: файл на диске пришлось бы чистить, а история глубже последних сотен строк для разбора сбоя не нужна. """ import logging import threading from collections import deque __all__ = ["LogBuffer", "install"] class LogBuffer(logging.Handler): def __init__(self, capacity: int = 500): super().__init__() self._records: deque = deque(maxlen=capacity) self._lock = threading.Lock() def emit(self, record: logging.LogRecord) -> None: try: message = record.getMessage() if record.exc_info: message += "\n" + self.format(record).split("\n", 1)[-1] except Exception: # noqa: BLE001 - журнал не должен падать сам message = "не удалось разобрать запись журнала" with self._lock: self._records.append({ "time": record.created, "level": record.levelname, "logger": record.name, "message": message, }) def tail(self, limit: int = 100, level: str | None = None) -> list[dict]: with self._lock: rows = list(self._records) if level: wanted = level.upper() rows = [r for r in rows if r["level"] == wanted] return rows[-limit:] _buffer: LogBuffer | None = None def install(capacity: int = 500) -> LogBuffer: """Подключает буфер к корневому журналу. Повторный вызов вернёт тот же буфер.""" global _buffer if _buffer is None: _buffer = LogBuffer(capacity) _buffer.setFormatter(logging.Formatter("%(message)s")) logging.getLogger().addHandler(_buffer) return _buffer def get() -> LogBuffer: return install()