5.6 KiB
5.6 KiB
1. 使用的方案
项目在前端(ESP32/DCU Web 控制台)实现了自研轻量级日志子系统,位于 js/common/log.js。其职责是:接收带标签的日志条目 → 去重/过滤 → 维护 DOM 面板 + 环形缓冲 → 按级别样式化渲染。
日志数据并非由前端直接构造所有业务事件,而是通过 js/common/polling.js 以轮询方式从后端 ESP32 设备拉取 /system/status 与 /logs 两条流,把后端的结构化输出注入到统一的 UI 中;同时各模块通过 addLog(level, tag, message) 主动写入调试类日志。因此该“logging system”是 后端实时日志拉取 + 前端集中式渲染 的组合。
2. 核心文件
js/common/log.js— 日志缓存、去重、DOM 渲染、过滤、自动滚动、条数上限裁剪的核心库(addLog、clearLogs、setLogFilter、toggleAutoScroll四导出)js/common/polling.js— 定时递归 setTimeout 调度/system/status与/logs轮询,并将远端行^[DIEW] ([^|]+)\|(.*)$解析为addLog(m[1], m[2], m[3])css/motor.css、components.css、can.css、layout.css、responsive.css、base.css—.log-line、.lt/.lv/.lg/.lm等日志元素的 CSS 样式pages/index.html、pages/can_monitor.html、pages/motor.html、tabs.html— 包含#logScroll、#logCount、#autoScrollBtn、.log-filter按钮的页面骨架(由log.js通过 ID 直接操作)- 消费端(导入
addLog的模块举例):js/app.js、js/common/connection.js、js/ble/state.js、js/ota/file-handle.js、js/ota/session.js、js/ota/upload-esp32.js、js/ota/upload-gd32.js、js/common/polling.js、js/ota/progress.js
3. 架构与设计约定
日志条目结构
每个日志是一个三元组 (level, tag, message),经 formatTime 加上本地时间戳,再被 HTML 转义(调用 utils.escapeHtml 防 XSS)后渲染为一行:
[时间] [级别] [TAG] 消息
其中级别 lv 使用 class D|I|W|E,tag 用 lg,消息用 lm。
去重策略
- 同一帧内相邻重复项:通过
makeLogHash(level, tag, message)计算字符串哈希并与lastLogHash比较,完全相同的连续条目只写一次。 - 用于避免高频率状态上报产生刷屏。
缓冲与裁剪
- 内存采用普通数组
logEntries,MAX_LOGS = 1000作为硬上限;超出时trimIfNeeded()移出最旧的 DOM 节点并 shift 掉对应 entry。 - 没有持久化历史到 localStorage。
过滤与 UI
- 全局
logFilter∈{ all, D, I, W, E },由页面#autoScrollBtn与.log-filter控制。 setLogFilter会清空#logScroll并按当前 filter 重建 Fragment 显示,支持运行时切换。toggleAutoScroll切换自动滚动开关,文字在 “🔽 自动滚动” / “⏸️ 暂停滚动” 间切换。- 自动滚动使用
requestAnimationFrame批量合并,防止每条日志都触发强制布局。
来源分流
- 后端实时流:
polling.js周期性 GET/logs,将每行正则^(.) (.+)\|(.+)$解析为 level/tag/message,直接addLog(...)投递。 - 前端主动日志:连接、OTA、BLE、文件处理等各模块遇到关键流程点(断线、重启、握手、校验失败、SHA 不匹配等)调用
addLog,按connection/系统/诊断/上传/文件/OTA/内存/BLE等 tag 区分领域。
后端协议契约(由 poller 侧 enforce)
/system/status返回 JSON,含sys_state_code、relay_state、堆低水位等字段。/logs返回文本行,格式约定为LEVEL TAG|MESSAGE(首字符为D/I/W/E,然后空格、tag、竖线、正文),这是前后端共享的契约,而非任何框架约定。
4. 约定与约束
- 唯一入口:全仓库前端日志通过
common/log.js暴露的四函数进出;外部模块仅 import/addLog,不允许自行创建日志 DOM。 - 必须带 tag:所有调用均传入二级分类 tag(如
'连接'、'诊断'、'上传'、'文件'、'内存'、'系统'、'BLE'、'OTA'),以便后期按 tag 过滤或筛选。 - 级别限定:level 严格为
D(debug)、I(info)、W(warn)、E(error) 其一;UI 据此设置 CSS 颜色与过滤器类别。 - XSS 防护:level/tag/message 插入 innerHTML 前全部经
escapeHtml处理;新增内容也需遵循此规则。 - 性能约束:单条日志路径禁用多次 DOM query;高频重复内容依赖去重;批量追加走
DocumentFragment(filter 重建时);自动滚动批量化到下一 rAF。 - 轮询间隔:
status与logs轮询默认 1000ms,上传期间暂停(pausePollingForUpload),GD32 中继期临时降至 400ms(setStatusPollIntervalMs(400))以减少日志滞后,结束后恢复。 - 页面前置条件:页面需存在
#logScroll、#logCount、#autoScrollBtn、.log-filter四个选择器目标,否则clearLogs/setLogFilter/toggleAutoScroll无作用但不会抛错。 - 未覆盖场景:Node.js / Python 后端脚本目录(
scripts/*.py、scripts/*.mjs)普遍使用原生console.*(见 grep 结果),不使用前端日志系统——说明本日志系统限定于浏览器端控制台。
5. 总结
这是一个围绕前端 DOM 的微型日志子系统:通过统一的 addLog(level,tag,message) 接口汇聚多模块的调试信息,并以环形缓冲 + 去重 + 自动滚动的形式挂载到页面上;同时通过 polling.js 把后端 ESP32 的 /logs 实时串流入这个面板,形成“设备端打 log + 前端集中呈现”的统一调试窗口。