feat: 核心模块增强 — 加密 + 运行时配置 + 日志缓冲
- crypto.py: API Key 加密/解密工具 - runtime_config.py: 运行时动态配置管理 - log_buffer.py: 内存日志缓冲区 - config.py: 新增加密配置项 - http_client.py: 增强重试和错误处理
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""内存日志缓冲:供后台「系统日志」页查看应用运行日志。
|
||||
|
||||
把应用日志(stdout)同时捕获到进程内环形缓冲(deque),提供级别/关键字/条数
|
||||
过滤查询。缓冲在进程重启后清零;需要持久化的审计请另行落库。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
_BUFFER: deque[dict] = deque(maxlen=2000)
|
||||
_LOCK = threading.Lock()
|
||||
|
||||
_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),
|
||||
}
|
||||
with _LOCK:
|
||||
_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()
|
||||
with _LOCK:
|
||||
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))
|
||||
Reference in New Issue
Block a user