logging: 全链路日志与完整异常堆栈

- 新增 logutil:DEBUG 全量滚动日志文件 + 控制台 INFO 简洁输出 + GUI 队列处理器
- providers:每次尝试失败记录 HTTP 状态/响应体,网络异常 logger.exception 留完整堆栈
- pipeline:单任务异常不拖垮整批,缓存命中/阶段耗时/停止过程全程留痕
- CLI/GUI 顶层兜底:未预期异常打印完整 traceback 并指引日志文件;GUI 新增「打开日志」按钮
- 新增 test_logutil(文件落盘/级别过滤/异常堆栈/队列),共 33 个测试全部通过
This commit is contained in:
yeuimu
2026-09-02 16:01:44 +08:00
parent 08d26710b4
commit 0033b7a4d2
8 changed files with 271 additions and 53 deletions
+28 -7
View File
@@ -8,6 +8,7 @@
import asyncio
import base64
import json
import logging
import os
import re
@@ -15,6 +16,8 @@ import aiohttp
from .config import AppConfig
logger = logging.getLogger("violation_detector.providers")
MIME = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
".webp": "image/webp", ".gif": "image/gif", ".bmp": "image/bmp",
".tiff": "image/tiff"}
@@ -114,6 +117,7 @@ async def detect_ark(session, sem, cfg: AppConfig, prompt: str, path: str, stop=
last_err = ""
for attempt in range(1, cfg.retries + 1):
if stop and stop():
logger.info("豆包检测 %s:用户停止", fname)
return {"file": fname, "provider": "doubao", "status": "error", "attr": "",
"logic": "", "category": "检测异常", "raw": "用户停止", "usage": {}}
try:
@@ -123,11 +127,17 @@ async def detect_ark(session, sem, cfg: AppConfig, prompt: str, path: str, stop=
data = await r.json()
content = data["choices"][0]["message"]["content"]
return _result(fname, "doubao", content, data)
last_err = f"HTTP {r.status}: {(await r.text())[:300]}"
except Exception as e: # noqa: BLE001
last_err = f"{type(e).__name__}: {e}"
body = (await r.text())[:2000]
last_err = f"HTTP {r.status}: {body[:300]}"
logger.warning("豆包检测 %s%d/%d次尝试失败:HTTP %s\n响应体:%s",
fname, attempt, cfg.retries, r.status, body)
except Exception: # noqa: BLE001 网络层异常:记录完整堆栈后重试
last_err = "网络/协议异常(详见日志)"
logger.exception("豆包检测 %s%d/%d次尝试抛出异常", fname, attempt, cfg.retries)
if attempt < cfg.retries:
logger.info("豆包检测 %s%.0f 秒后重试", fname, 3 * attempt)
await asyncio.sleep(3 * attempt)
logger.error("豆包检测 %s 最终失败:%s", fname, last_err)
return {"file": fname, "provider": "doubao", "status": "error", "attr": "",
"logic": "", "category": "检测异常", "raw": last_err, "usage": {}}
@@ -156,6 +166,7 @@ async def detect_deepseek(session, sem, cfg: AppConfig, prompt: str, path: str,
last_err = ""
for attempt in range(1, cfg.retries + 1):
if stop and stop():
logger.info("DeepSeek 检测 %s:用户停止", fname)
return {"file": fname, "provider": "deepseek", "status": "error", "attr": "",
"logic": "", "category": "检测异常", "raw": "用户停止", "usage": {}}
try:
@@ -167,12 +178,22 @@ async def detect_deepseek(session, sem, cfg: AppConfig, prompt: str, path: str,
if content and content.strip():
return _result(fname, "deepseek", content, data)
# 推理烧尽预算会得到空正文,按可重试错误处理
last_err = "empty content (reasoning exhausted max_tokens?)"
fr = data["choices"][0].get("finish_reason")
last_err = f"empty content (finish_reason={fr}, max_tokens={cfg.max_tokens})"
logger.warning("DeepSeek 检测 %s%d/%d次尝试返回空正文"
"finish_reason=%s,推理可能耗尽 max_tokens=%s",
fname, attempt, cfg.retries, fr, cfg.max_tokens)
else:
last_err = f"HTTP {r.status}: {(await r.text())[:300]}"
except Exception as e: # noqa: BLE001
last_err = f"{type(e).__name__}: {e}"
body = (await r.text())[:2000]
last_err = f"HTTP {r.status}: {body[:300]}"
logger.warning("DeepSeek 检测 %s%d/%d次尝试失败:HTTP %s\n响应体:%s",
fname, attempt, cfg.retries, r.status, body)
except Exception: # noqa: BLE001 网络层异常:记录完整堆栈后重试
last_err = "网络/协议异常(详见日志)"
logger.exception("DeepSeek 检测 %s%d/%d次尝试抛出异常", fname, attempt, cfg.retries)
if attempt < cfg.retries:
logger.info("DeepSeek 检测 %s%.0f 秒后重试", fname, 3 * attempt)
await asyncio.sleep(3 * attempt)
logger.error("DeepSeek 检测 %s 最终失败:%s", fname, last_err)
return {"file": fname, "provider": "deepseek", "status": "error", "attr": "",
"logic": "", "category": "检测异常", "raw": last_err, "usage": {}}