"""内存日志缓冲:捕获所有 audio2text.* 日志到有界 deque,供 /logs 页面查询。 设计: - MemoryLogHandler 挂到 `audio2text` logger,子 logger 的记录经传播自动汇入。 - deque(maxlen=N) 有界,旧记录自动淘汰,避免内存无限增长。 - emit() 在 logging 内部锁下执行,deque.append 线程安全。 - 查询时按级别过滤(≥指定级别)、取最近 N 条,返回结构化 dict。 """ from __future__ import annotations import logging import threading from collections import deque from datetime import datetime, timezone # 级别名 → 数值,用于过滤 _LEVELS = { "debug": logging.DEBUG, "info": logging.INFO, "warning": logging.WARNING, "error": logging.ERROR, } class _BoundedLogRecord: """从 LogRecord 提取显示所需字段,避免持有完整对象引用。""" __slots__ = ("ts", "level_no", "level_name", "logger_name", "message", "traceback") def __init__(self, record: logging.LogRecord) -> None: self.ts: float = record.created self.level_no: int = record.levelno self.level_name: str = record.levelname self.logger_name: str = record.name # getMessage() 应用 %-style 懒格式化 self.message: str = record.getMessage() # exc_info 存的是 (type, value, tb) 三元组,格式化为字符串 self.traceback: str | None = None if record.exc_info: import traceback as _tb self.traceback = "".join(_tb.format_exception(*record.exc_info)) class MemoryLogHandler(logging.Handler): """把日志记录存入有界 deque,供 /api/logs 查询。""" def __init__(self, buffer_size: int = 2000) -> None: super().__init__() self._buffer: deque[_BoundedLogRecord] = deque(maxlen=buffer_size) self._lock = threading.Lock() def emit(self, record: logging.LogRecord) -> None: try: entry = _BoundedLogRecord(record) with self._lock: self._buffer.append(entry) except Exception: # pragma: no cover —— logging 不应抛 self.handleError(record) def get_records( self, level: str = "info", tail: int = 200, ) -> list[dict]: """返回最近 tail 条、≥指定级别的日志(JSON 友好的 dict 列表)。 Args: level: debug | info | warning | error(最小级别) tail: 最多返回条数 """ min_level = _LEVELS.get(level.lower(), logging.INFO) with self._lock: snapshot = list(self._buffer) # 过滤 + 取最近 tail 条 filtered = [r for r in snapshot if r.level_no >= min_level] result = [] for r in filtered[-tail:]: ts_str = datetime.fromtimestamp(r.ts, tz=timezone.utc).strftime( "%Y-%m-%d %H:%M:%S" ) result.append({ "ts": ts_str, "level": r.level_name, "logger": r.logger_name, "msg": r.message, "traceback": r.traceback, }) return result def clear(self) -> None: with self._lock: self._buffer.clear() # 进程级单例 _buffer: MemoryLogHandler | None = None def get_log_buffer() -> MemoryLogHandler: global _buffer if _buffer is None: _buffer = MemoryLogHandler() return _buffer def init_log_buffer(buffer_size: int) -> MemoryLogHandler: """(重新)创建缓冲并返回,供 main.py 启动时按配置初始化。""" global _buffer _buffer = MemoryLogHandler(buffer_size=buffer_size) return _buffer