feat(日志): 日志页支持关键字/级别/时间过滤 + 下载(含滚动历史)

「日志」页原来只能 tail 固定行数、且文件下拉写死了 4 项(新增的 notify.log
根本选不到)。本次:

- core/logger.py 新增读取接口:list_log_files()(模块文件 + .1/.2 滚动历史,
  白名单)、query_log()(关键字/最低级别/时间过滤,返回结构化行)、
  read_log_text()(导出)。
  · 时间/级别过滤对**续行**用继承值(traceback 缩进行本身没有前缀),
    按 ERROR 筛不会把堆栈拆散;
  · 过滤按"最近 N 条命中"返回,保持文件原顺序;
- GET /api/logs 换新协议(rows/files/matched/scanned/truncated),
  新增 GET /api/logs/download(附件下载,支持同样过滤;无一条件时整文件);
- 顺带修 **目录穿越**:原实现 os.path.join(_LOG_DIR, file) 可用 ../../ 逃逸,
  现在 file 必须命中白名单;
- 顺带修 web/admin_api.py 里 _log 未定义(用户增删改的日志行会 NameError);
- 前端:文件下拉改为按接口渲染、加关键字(命中高亮)/级别/时间范围/下载/重置,
  自动刷新时不再把正在上翻的用户拽回底部。

文档:doc/API.md §10.1(参数与返回)、README 日志节。
This commit is contained in:
2026-09-16 08:44:19 +08:00
parent 3b6ec8c98f
commit 2c32c397c8
6 changed files with 387 additions and 38 deletions
+24 -6
View File
@@ -112,7 +112,13 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
.log-content .log-err{color:#f87171}
.log-content .log-warn{color:#fbbf24}
.log-content .log-info{color:#67e8f9}
.log-toolbar{display:flex;gap:10px;margin-bottom:12px;align-items:center}
.log-row{white-space:pre-wrap}
.log-cont{color:#8a94a6;padding-left:2.2em} /* 续行(traceback 缩进行):淡色缩进 */
.log-hit{background:#fbbf2433} /* 关键字命中高亮 */
/* 工具栏:控件较多,允许换行;控件本身不参与收缩(否则会被挤成竖排文字) */
.log-toolbar{display:flex;flex-wrap:wrap;gap:8px 10px;margin-bottom:12px;align-items:center}
.log-toolbar>*{flex:0 0 auto}
.log-toolbar .hint{flex:1 1 200px;min-width:160px}
/* 区块标题 */
.section-title{font-family:var(--display);font-size:15px;font-weight:700;color:var(--text);margin:0 0 12px;padding-bottom:8px;border-bottom:2px solid var(--primary);display:inline-block;letter-spacing:.2px}
@@ -780,20 +786,32 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
<div class="page-title">日志查看</div>
<div class="page-sub">查看系统各模块运行日志</div>
<div class="log-toolbar">
<select id="log-file" class="form-control" style="width:auto" onchange="loadLogs()">
<option value="core.log">核心日志 (core.log)</option>
<option value="task.log">任务日志 (task.log)</option>
<option value="web.log">Web日志 (web.log)</option>
<option value="action.log">操作日志 (action.log)</option>
<!-- 文件下拉由 /api/logs 返回的清单动态渲染(含 .1/.2 滚动历史与 notify.log) -->
<select id="log-file" class="form-control" style="width:auto" onchange="loadLogs()"></select>
<input id="log-q" class="form-control" style="width:190px" placeholder="关键字(设备号/包名/报错…)"
onkeydown="if(event.key==='Enter')loadLogs()">
<select id="log-level" class="form-control" style="width:auto" onchange="loadLogs()">
<option value="">全部级别</option>
<option value="ERROR">仅 ERROR</option>
<option value="WARNING">WARNING 及以上</option>
<option value="INFO">INFO 及以上</option>
</select>
<input id="log-since" type="datetime-local" class="form-control" style="width:auto"
title="起始时间" onchange="loadLogs()">
<span class="text-muted">~</span>
<input id="log-until" type="datetime-local" class="form-control" style="width:auto"
title="结束时间" onchange="loadLogs()">
<select id="log-lines" class="form-control" style="width:auto" onchange="loadLogs()">
<option value="100">最近 100 行</option>
<option value="300" selected>最近 300 行</option>
<option value="500">最近 500 行</option>
<option value="1000">最近 1000 行</option>
<option value="5000">最近 5000 行</option>
</select>
<label class="form-check"><input type="checkbox" id="log-auto" checked onchange="toggleLogAuto()"> 自动刷新</label>
<button class="btn" onclick="loadLogs()">刷新</button>
<button class="btn" onclick="downloadLogs()" title="下载当前筛选结果(无筛选即整个文件)">下载</button>
<button class="btn" onclick="resetLogFilters()">重置</button>
<span class="hint" id="log-hint"></span>
</div>
<div id="log-content" class="log-content"></div>