P0: ORM-迁移同步 - 新增 RawEvent/IngestFailure/DataQualityCheck/DataLineage 4 个模型类 - MatchStats 补充 xg_source/xg_updated_at/xg_source_record_id 字段 - 修复 server_default=_utcnow → func.now()(4 处) - BigInteger 导入 P1: - log_buffer.py 移除 threading.Lock(asyncio 单线程下无需锁) - 确认 context_builder/understat/injuries 等已有修复 P2: - Dockerfile 新增非 root 用户 + .dockerignore - 前端 fetchDashboard 修复 total 字段(改用 items.length) - 前端 fetchSystemConfig 改用真实 /admin/settings 端点 - 修复 validation.py return self"" → return self P3: - predict.py 缓存加 _CACHE_MAX_SIZE=200 淘汰 - eval.py get_eval_summary 加 limit 参数(默认 1000)+ 返回 total_settled - orchestrator.py agent provider 配置缓存 60s
79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""内存日志缓冲:供后台「系统日志」页查看应用运行日志。
|
|
|
|
把应用日志(stdout)同时捕获到进程内环形缓冲(deque),提供级别/关键字/条数
|
|
过滤查询。缓冲在进程重启后清零;需要持久化的审计请另行落库。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections import deque
|
|
|
|
_BUFFER: deque[dict] = deque(maxlen=2000)
|
|
|
|
_LEVEL_ORDER = {"DEBUG": 10, "INFO": 20, "WARNING": 30, "ERROR": 40, "CRITICAL": 50}
|
|
|
|
|
|
class MemoryLogHandler(logging.Handler):
|
|
"""把日志记录写入内存环形缓冲。"""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
# format() 会在有 exc_info 时自动附带异常堆栈文本
|
|
self.setFormatter(logging.Formatter("%(message)s"))
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
try:
|
|
entry = {
|
|
"ts": record.created,
|
|
"level": record.levelname,
|
|
"logger": record.name,
|
|
"message": self.format(record),
|
|
}
|
|
_BUFFER.append(entry)
|
|
except Exception: # noqa: BLE001 日志采集绝不影响业务
|
|
self.handleError(record)
|
|
|
|
|
|
class _SQLNoiseFilter(logging.Filter):
|
|
"""过滤 SQLAlchemy 的 DEBUG/INFO 回显(只留警告以上)。"""
|
|
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
return not (
|
|
record.name.startswith("sqlalchemy.") and record.levelno < logging.WARNING
|
|
)
|
|
|
|
|
|
def get_entries(
|
|
min_level: str | None = None,
|
|
keyword: str | None = None,
|
|
limit: int = 200,
|
|
) -> list[dict]:
|
|
"""按条件查询缓冲日志,最新在前。"""
|
|
min_no = _LEVEL_ORDER.get((min_level or "").upper(), 0)
|
|
kw = (keyword or "").strip().lower()
|
|
items = list(_BUFFER)
|
|
items.reverse()
|
|
out: list[dict] = []
|
|
for e in items:
|
|
if _LEVEL_ORDER.get(e["level"], 0) < min_no:
|
|
continue
|
|
if kw and kw not in e["message"].lower() and kw not in e["logger"].lower():
|
|
continue
|
|
out.append(e)
|
|
if len(out) >= limit:
|
|
break
|
|
return out
|
|
|
|
|
|
def setup_memory_logging(level: str = "INFO") -> None:
|
|
"""挂载内存 handler 到 root logger(幂等),并确保 root 级别不低于 INFO。"""
|
|
root = logging.getLogger()
|
|
if any(isinstance(h, MemoryLogHandler) for h in root.handlers):
|
|
return
|
|
handler = MemoryLogHandler()
|
|
handler.setLevel(logging.INFO)
|
|
handler.addFilter(_SQLNoiseFilter())
|
|
root.addHandler(handler)
|
|
if root.level == logging.NOTSET or root.level > logging.INFO:
|
|
root.setLevel(getattr(logging, level.upper(), logging.INFO))
|