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:
+33
-2
@@ -139,7 +139,8 @@
|
||||
| POST | `/api/users` | Admin | 新建用户 |
|
||||
| PUT | `/api/users/<int:uid>` | Admin | 更新用户(改密/权限/管理员) |
|
||||
| DELETE | `/api/users/<int:uid>` | Admin | 删除用户 |
|
||||
| GET | `/api/logs` | G | 读日志文件尾部 N 行 |
|
||||
| GET | `/api/logs` | G | 读日志(关键字/级别/时间过滤,返回结构化行) |
|
||||
| GET | `/api/logs/download` | G | 下载日志(同样支持过滤;无过滤即整个文件) |
|
||||
|
||||
### 2.5 devices(`web/devices_api.py`)
|
||||
|
||||
@@ -615,7 +616,37 @@
|
||||
| `POST /api/users` | Admin | `{"username","password","is_admin"?,"perms"?}` | 用户名/密码空、重名 → 400 |
|
||||
| `PUT /api/users/<int:uid>` | Admin | `{"password"?,"is_admin"?,"perms"?}` | 取消最后一个管理员 → 400;不存在 404 |
|
||||
| `DELETE /api/users/<int:uid>` | Admin | — | 删 `admin`/删自己/删最后一个管理员 → 400 |
|
||||
| `GET /api/logs?file=core.log&lines=300` | G | — | 返回日志尾部 + 可选文件清单 |
|
||||
| `GET /api/logs?file=core.log&lines=300` | G | `q` 关键字、`level` 最低级别、`since`/`until` 时间 | 见下 §10.1 |
|
||||
| `GET /api/logs/download?file=core.log` | G | 同上(参数一致) | 以附件返回 `.txt`,文件名带时间戳 |
|
||||
|
||||
#### 10.1 日志查询(`GET /api/logs`)
|
||||
|
||||
参数:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `file` | 日志文件名,**白名单校验**(只能取接口返回的 `files[].name`,含 `.1/.2` 滚动历史);非法值返回 `ok:false` |
|
||||
| `lines` | 返回**最近**多少条命中(默认 300,上限 5000) |
|
||||
| `q` | 关键字,大小写不敏感,匹配整行原文 |
|
||||
| `level` | 最低级别:`ERROR` / `WARNING` / `INFO` / `DEBUG`(含以上);留空为全部 |
|
||||
| `since` / `until` | 时间范围,接受 `YYYY-MM-DDTHH:MM`(datetime-local)或 `YYYY-MM-DD`(按整天) |
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{"ok": true, "file": "core.log",
|
||||
"files": [{"name": "core.log", "label": "核心(adb/调度) · core.log",
|
||||
"size": 1175910, "mtime": 1789519242, "rotated": false}],
|
||||
"rows": [{"ts": "2026-09-16 08:26:50", "level": "INFO",
|
||||
"module": "core.disc", "msg": "发现: …", "cont": false}],
|
||||
"matched": 11423, "scanned": 11423, "truncated": true}
|
||||
```
|
||||
|
||||
- `rows` 保持文件原顺序(旧→新),即"最近 N 条命中"按时间正序排列;
|
||||
- `cont=true` 是**续行**(traceback 的缩进行等):它本身没有时间/级别前缀,
|
||||
过滤时**继承上一条带前缀的行**,所以按 ERROR 筛选不会把堆栈拆散;
|
||||
- `matched` 是命中总数,`truncated=true` 表示更早的命中没返回(应缩小时间范围或加关键字);
|
||||
- `scanned` 是实际扫描行数(上限 50 万行)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user