feat: 分级拦截日志 + 两类日志文件;成绩录入防呆/解析/宽度修复
## 日志(用户要求:分级 + 每个拦截都要有指出代码行的调试日志 + 两类文件)
- 新增 internal/logging:拦截点捕获(file:line + 函数名,相对路径)、两类文件轮转、
按级别/类别分流。httpapi 与 middleware 各自薄封装,无循环依赖。
- 两类目录(数据目录下):
operations/ 放行类(INFO「操作已放行」+ 常规运行日志,按 log.level)
interceptions/ 拦截类(WARN 中文原因 + 拦截点 + method/path/actor/role/requestId、
DEBUG 上下文、ERROR 服务端异常)—— **固定 debug 采集**,
否则默认 info 下"每次拦截的调试日志"根本看不见
- 命名 operations-YYYYMMDD[.N].log,按天+按大小轮转,每类保留 keepFiles 份;写盘失败
降级只写控制台且只 Warn 一次。
- 所有业务错误统一走 newAPIError 捕获拦截点(11 处内联 &apiError{} 已收进构造函数,
防止绕过);中间件层拦截(401/403/CSRF/未改初始密码/429)也留痕,响应体与状态码一字未变。
- config.yaml 的 log 段新增 dir / maxSizeMB / keepFiles(留空即默认)。
- 服务端包数 12 → 13(新增 internal/logging)。
## 成绩录入(用户实机踩坑)
- 真因一:成绩格 placeholder 是光秃秃的 `1:06.33`,而全仓库没有任何 ::placeholder 样式,
示例文本与真实输入长得一样 → 用户把灰字当成已填内容,那 13 格其实是空的。
- 真因二:2 条报错是秒位 ≥ 60(1:70)或全角冒号(1:06);1:06 与 1:06.00 在解析器里
本来就等价(都 = 66),格式不是问题。
- 真因三:成绩格没有宽度定义(旁边名次 72px、状态 110px 都有),被挤到第 4 位数字看不见。
修复:parseSecondsDetail 区分 empty/seconds-overflow/unrecognized;新增 entryGuards.ts
纯函数(录入页与审核页共用),报错**打出那一格的实际内容**并说明原因;解析器新认全角
冒号/全角数字/1分06秒 等写法;成绩格 min-width 112px(按 fontTools 实测文本宽度定);
示例文本改 `例 1:06.33` 等并给录入表格内的 ::placeholder 加淡色斜体;新增
onBeforeRouteLeave 离开守卫(原来切菜单会静默丢未保存成绩)。
测试:服务端 13 包全绿(372 个测试函数);前端 vue-tsc 通过、205 例全过;Console 18 文件。
docs/ 与 devdocs/ 同步(FAQ 到 Q76,部署指南新增第 10 节「服务端日志与拦截复盘」)。
This commit is contained in:
1 parent
8e707e1e9b
commit
e5e0944d26
32 files changed
+2017
-138
No files matched your search
@@ -57,6 +57,7 @@
|
||||
## 6. 数据与备份
|
||||
|
||||
- [ ] 确认数据目录 `%LOCALAPPDATA%\ScoreboardConsole\data` 下的 `backups\` 里已出现 `system-auto-*.db` 与 `event-auto-*.db`(默认每 5 分钟)
|
||||
- [ ] 确认数据目录下已生成 `logs\operations\` 与 `logs\interceptions\`(服务端日志,见部署指南第 10 节),并知道操作被拒时去 `interceptions\` 当天文件里搜原因与拦截点
|
||||
- [ ] 开幕前手动点一次「立即备份」(「系统设置 → 备份 → 立即备份」)
|
||||
- [ ] 把整个数据目录(不只是 `backups\`:`event_projs\<当前项目名>\uploads\` 里还有会标、背景图、单位 logo 与上传的字体)拷一份到 U 盘
|
||||
- [ ] 留一份成绩总表存档:到「导出 → 结果导出」导一份**全部(单页)**(`赛事成绩总表(单页).xlsx`,一个工作簿一张表,适合比对与筛选),需要归档打印再导一份「全部」(多 sheet)
|
||||
|
||||
+18
-3
@@ -76,11 +76,19 @@
|
||||
|
||||
| 列 | 操作 |
|
||||
| --- | --- |
|
||||
| 成绩输入 | 直接输入成绩。时间类项目按 `1:06.33` 格式;工具栏会提示当前单位(秒 / 米 / 次 / 分) |
|
||||
| 成绩输入 | 直接输入成绩;工具栏会提示当前单位(秒 / 米 / 次 / 分),成绩格比过去更宽,四位数也看得全 |
|
||||
| 名次 | 自动排名项目由系统自动计算,不可编辑;手动名次项目(录入方式为「手动名次」或比较方式为「不比较」)可直接填名次 |
|
||||
| 状态 | 下拉选择:**有效**、**DNS 弃赛**、**DQ 取消**、**DNF 未完赛**、**退赛** |
|
||||
| 积分(自动) | 随成绩实时计算,仅供参考 |
|
||||
| 备注 | 自由填写 |
|
||||
| 备注 | 自由填写,示例文本是 `可留空` |
|
||||
|
||||
**成绩写法**:能接受的写法比过去多,中文输入法下的全角冒号 `1:06`、全角数字也认。
|
||||
|
||||
- 时间类(单位「秒」):`66`、`66.33`、`1:06`、`1:06.33`、`1:6`、`1分06秒`、`1分6秒`、`1分06`、`1小时2分3秒`;
|
||||
- 距离 / 计数 / 得分类:按工具栏提示的单位填数字,如米 `12.34`、次 `12`、分 `96.5`;
|
||||
- 秒位 ≥ 60 仍然拒绝(`1:70` 不是 70 秒),但报错会写明原因(见 4.4)。
|
||||
|
||||
**示例文本不是已填内容**:空格子里显示的 `例 1:06.33`(距离 `例 12.34`、计数 `例 12`、得分 `例 96.5`)是**更淡的斜体灰字**,只是告诉你大概怎么写,**不代表这一格已经有成绩**。灰字不会被提交;点发布时如果那一格还是灰字,就按「成绩为空」拦下。过去示例文本长得和真实输入一样,容易看成「我已经填了」。
|
||||
|
||||
右侧「实时积分预览」显示:若现在发布,各单位积分的变化、最大分差、受影响单位数。
|
||||
|
||||
@@ -104,7 +112,11 @@
|
||||
|
||||
页面下方「防呆校验」会实时列出问题:
|
||||
|
||||
- **错误(阻止发布)**:缺少成绩、成绩格式无法解析、手动名次项目缺少名次;
|
||||
- **错误(阻止发布)**:成绩格有三种原因,文案都会**把那一格的实际内容原样打出来**,并对症给出改法,不会只说一句「缺少成绩」让你去改本来就对的格式:
|
||||
- 空格子:`第 3 行 张三:成绩为空(当前「(空)」),请填写成绩`;
|
||||
- 秒位 ≥ 60:`第 3 行 张三:秒位不能 ≥ 60(当前「1:70」),请写成 1:06 或 66 这类`;
|
||||
- 写法无法识别:`第 3 行 张三:成绩写法无法识别(当前「约1分06秒」),可用 66、66.33、1:06、1:06.33、1分06秒`——非时间单位会换成对应的数字提示(不会拿时间格式去教距离项目);
|
||||
- 另外还有:手动名次项目缺少名次。
|
||||
- **提醒(不阻止发布)**:成绩 ≤ 0、名次重复(请确认是否并列)、成绩与赛会纪录差距过大。
|
||||
|
||||
有错误时点「发布」会被拦下并提示第一处问题。
|
||||
@@ -112,6 +124,7 @@
|
||||
### 4.5 存草稿与发布
|
||||
|
||||
1. **存草稿**:保存当前组别的成绩,草稿**不会上屏**,也不计入积分榜;有未保存修改时页面会显示「有未保存修改」徽标。
|
||||
- **未保存的成绩不会被静默丢掉**:有未保存修改时,点左侧菜单切到别的页面(以及浏览器前进 / 后退)会先弹出确认框,确认丢弃才离开;关闭或刷新浏览器时浏览器也会原生提示;切换项目本来就会先确认。选「取消」留在本页,改动都还在。
|
||||
2. **发布**:点击后弹出确认框,显示本次影响的单位数与最大分差;确认后成绩立即上屏并重算积分榜。
|
||||
- 发布前系统会自动把当前内容整组保存为草稿再发布,无需手动先存;
|
||||
- 示例数据模式下「发布」按钮不可用,避免把未落库的改动当成发布成功。
|
||||
@@ -530,6 +543,8 @@
|
||||
4. 筛选与分页作用于最近 200 条记录;
|
||||
5. 该页面仅管理员可查看。
|
||||
|
||||
> 本页是**业务审计**(谁在什么时候改了什么),只记**放行**的操作。想看「某次操作为什么被系统拦下」——比如保存被拒、导入报错、切页提示离线——那要看服务端写在数据目录 `logs\` 下的**两类日志文件**(放行类 / 拦截类):我们主动拦下的报错会写明中文原因、拦截点 `文件:行 函数名`、操作人与 requestId。位置、轮转与复盘步骤见[部署指南 · 服务端日志与拦截复盘](./部署指南.md#10-服务端日志与拦截复盘)。
|
||||
|
||||
---
|
||||
|
||||
## 14. 系统设置
|
||||
|
||||
+37
-2
@@ -37,7 +37,7 @@
|
||||
撤销发布 → 修改 → 重新发布。撤销会作废该组积分流水,大屏回退到发布前状态,过程可在「变更历史」查看。
|
||||
|
||||
**Q9:为什么「发布」是灰的?**
|
||||
常见原因:未登录、服务端断开、示例数据模式,或存在阻止发布的校验错误(缺少成绩、格式无法解析、手动名次项目缺名次)。
|
||||
常见原因:未登录、服务端断开、示例数据模式,或存在阻止发布的校验错误(成绩为空、秒位 ≥ 60、成绩写法无法识别、手动名次项目缺名次)。成绩格的报错现在会把**那一格的实际内容**打出来并对症给改法,照着提示改即可(见 Q75、Q76)。
|
||||
|
||||
**Q10:并列名次怎么处理?**
|
||||
成绩相同(或手动名次相同)自动按并列处理,显示同一名次。默认口径是「顺延」:并列第 3 各得第 3 名的分,无第 4 名,下一名第 5 名;奖牌同样并列(并列第 1 各记 1 金、无银)。具体口径由规则包配置决定。
|
||||
@@ -47,7 +47,7 @@
|
||||
唯一例外:「个人+团体」小项若把平均值口径选成「全部报名人员都计入」,这些人在队内平均里会按 0 分占一个分母名额(见 Q15)。
|
||||
|
||||
**Q12:时间类成绩怎么写?**
|
||||
按 `分:秒.毫秒` 形式输入,例如 `1:06.33`;工具栏会显示当前项目的单位。
|
||||
按 `分:秒.毫秒` 形式输入最直观,例如 `1:06.33`;工具栏会显示当前项目的单位。实际上能接受的写法比过去多,中文输入法下的全角冒号 `1:06`、全角数字也认,完整清单见 Q76。成绩格里的 `例 1:06.33` 是**更淡的斜体灰字**,只是示例、不是已填内容(见 Q75)。
|
||||
|
||||
**Q13:能批量录入吗?**
|
||||
可以。支持从 Excel 复制后粘贴:两列及以上时第一列按单位名称 / 简称匹配行、最后一列作为成绩;单列时从当前行向下依次填充。也可以用 `Tab` / `Enter` 在格子间移动,`Ctrl+Enter` 存草稿。
|
||||
@@ -407,3 +407,38 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
|
||||
|
||||
**Q72:个人积分明细导出的数字跟个人积分榜对得上吗?会不会受榜单口径系数影响?**
|
||||
对得上,而且**不受系数影响**。「导出个人明细」(`个人积分总明细.xlsx`)里的 `得分` 是**记到个人的原始分**,与「个人积分榜」逐个数字一致;「基础数据 → 积分模板 → 榜单口径」里的个人项目得分折算系数(键名 `standings.individual_to_team_weight`,默认 1)只在汇总环节把个人分折算进单位总分,**不会改变个人明细里的数字**——比如把系数改成 0.5,个人明细的得分仍是原始分,只有单位总分变小(见 Q18)。
|
||||
|
||||
**Q73:操作被拒绝了,怎么查是谁在什么时候、因为什么被拦的?**
|
||||
服务端把「主动拦截」单独记到数据目录的拦截日志里(默认 `%LOCALAPPDATA%\ScoreboardConsole\data\logs\interceptions\`):
|
||||
|
||||
1. 找当天的 `interceptions-YYYYMMDD.log`(同一天有续写文件时按 `.1`、`.2` 往后看);
|
||||
2. 搜 `请求被拦截`(WARN)那一行,它已经带齐:`reason` 中文原因、`origin` **拦截点**(形如 `internal/httpapi/repo_events.go:231 normalizeAvgMode`,直接看出是哪个函数的哪一行拦下的)、`code` / `method` / `path`,以及 `actor`(`用户名#id`,未登录是 `guest`)/ `role` / `requestId`;
|
||||
3. 想定位得更准:浏览器开发者工具 **Network** 面板点开那条失败请求,复制响应头 `X-Request-Id`,拿它去日志文件里搜;同值的 DEBUG 行「拦截上下文」还会补 `detail` 与来源 IP;
|
||||
4. 想对照同一时间有没有成功的操作,去 `operations\` 里搜同一个 `requestId`(放行日志带 action / 实体 / 操作人)。
|
||||
|
||||
中间件层的拦截(未登录 401、无权限 403、CSRF 校验失败、未改初始密码、操作过于频繁 429)同样记在这里。完整说明见部署指南第 10 节。
|
||||
|
||||
**Q74:日志文件在哪?会不会把盘写满?**
|
||||
服务端日志在**数据目录**下的 `logs\`,分两类:
|
||||
|
||||
- `operations\`(放行类):放行的写操作(`操作已放行`,含 action / 实体 / 操作人 / requestId)+ 启动、数据库迁移等常规运行日志,按 `log.level` 过滤(默认 `info`);
|
||||
- `interceptions\`(拦截类):我们主动拦下的报错 + 服务端异常(5xx),**固定按 debug 采集,不受 `log.level` 影响**。
|
||||
|
||||
不会写满:文件名按天(`interceptions-20260911.log`),同一天写满一个文件后续写 `.1`、`.2`…;每类保留份数**含当前文件**,超出自动删最旧的。单文件上限与保留份数由 `config.yaml` 的 `log.maxSizeMB`(默认 20 MB)、`log.keepFiles`(默认 10)决定,两类合计最多约 `2 × keepFiles × maxSizeMB`(默认约 400 MB);目录可用 `log.dir` 改(留空 = `<数据目录>\logs`)。日志目录不可写时,服务端只告警一次并降级为只写控制台,不影响业务。
|
||||
|
||||
别和 Console 自己的 `logs\console.log`(在安装目录里)混淆:那个装的是 Console 与服务端的控制台输出;服务端日志文件在数据目录。
|
||||
|
||||
**Q75:为什么我明明填了成绩,发布却说缺少成绩?**
|
||||
最可能是把格子里的**灰字示例文本**当成了已填内容。过去成绩格的示例是 `1:06.33`,没有样式区分,看起来就像已经填好;现在示例统一带「例」字(时间 `例 1:06.33`、距离 `例 12.34`、计数 `例 12`、得分 `例 96.5`,备注格是 `可留空`),并且是**更淡的斜体灰字**,和真实输入明显不一样。灰字不会被提交,那一格仍然是空的。
|
||||
|
||||
新版报错也会自证:会写 `第 3 行 张三:成绩为空(当前「(空)」),请填写成绩`,把那一格的实际内容原样打出来。填了但写法不认的是另外两条:`秒位不能 ≥ 60(当前「1:70」)…` 或 `成绩写法无法识别(当前「约1分06秒」)…`,各自带内容与可用写法(见 Q76)。
|
||||
|
||||
**Q76:成绩能怎么填?**
|
||||
按项目单位填(工具栏会显示当前单位:秒 / 米 / 次 / 分):
|
||||
|
||||
- **时间类(秒)**:`66`、`66.33`、`1:06`、`1:06.33`、`1:6`、`1分06秒`、`1分6秒`、`1分06`、`1小时2分3秒` 都认;中文输入法下的**全角冒号 `1:06` 与全角数字**也认;
|
||||
- **距离 / 计数 / 得分类**:按提示填数字,例如米 `12.34`、次 `12`、分 `96.5`;无法识别时的提示会换成对应的数字说明,不会拿时间格式去教距离项目;
|
||||
- **秒位 ≥ 60 会被拒**(`1:70` 不是 70 秒),但原因写明:`秒位不能 ≥ 60(当前「1:70」),请写成 1:06 或 66 这类`;
|
||||
- 成绩格已经加宽,四位数也能看全。
|
||||
|
||||
有未保存的成绩时,切到别的菜单页面会先弹确认框,不会静默丢弃。
|
||||
+70
-7
@@ -13,7 +13,7 @@
|
||||
| 网络 | 完全离线即可运行。若需要手机/平板/电视访问,用独立交换机或路由器组成局域网 |
|
||||
| 磁盘 | 建议预留 ≥ 10GB |
|
||||
| 运行时 | 无需安装 Go / Node / 数据库等任何运行时,前端已内嵌进可执行文件 |
|
||||
| 权限 | 首次运行会在 `%LOCALAPPDATA%\ScoreboardConsole\` 下建数据目录并写入配置/规则(见 3.5),在部署目录的 `logs\` 写日志,请确保这两处可写 |
|
||||
| 权限 | 首次运行会在 `%LOCALAPPDATA%\ScoreboardConsole\` 下建数据目录并写入配置/规则(见 3.5),并在其下 `data\logs\` 写服务端日志(见第 10 节);Console 自己还在部署目录的 `logs\` 写 `console.log`,请确保这两处可写 |
|
||||
|
||||
> 服务端只构建 Windows amd64 一个版本。Linux / 其他平台不在交付范围内。
|
||||
|
||||
@@ -53,7 +53,7 @@ deploy\build.bat
|
||||
3. 安装完成后,桌面会出现名为**「赛事积分榜」**的快捷方式。
|
||||
4. 双击「赛事积分榜」启动。
|
||||
|
||||
> 赛事数据、配置与规则都存放在 `%LOCALAPPDATA%\ScoreboardConsole\`(不随安装目录走,见 3.4、3.5),日志写在安装目录的 `logs\` 下;安装目录本身只要当前用户可写即可。
|
||||
> 赛事数据、配置与规则都存放在 `%LOCALAPPDATA%\ScoreboardConsole\`(不随安装目录走,见 3.4、3.5);服务端日志在数据目录的 `data\logs\` 下,Console 自己的 `console.log` 在安装目录的 `logs\` 下(见第 10 节);安装目录本身只要当前用户可写即可。
|
||||
|
||||
### 3.2 使用免安装包
|
||||
|
||||
@@ -91,12 +91,14 @@ deploy\build.bat
|
||||
|
||||
```
|
||||
%LOCALAPPDATA%\ScoreboardConsole\ # 用户数据目录(与安装目录无关)
|
||||
├── data\ # 数据库、备份、上传的图片
|
||||
├── data\ # 数据库、备份、上传的图片、服务端日志(data\logs\)
|
||||
├── console\config.json # Console 自己的配置(实际生效的那份)
|
||||
├── config.yaml # 服务端配置(实际生效的那份)
|
||||
└── rules\ # 规则包(实际生效的那份)
|
||||
```
|
||||
|
||||
> 服务端日志默认落在 `data\logs\` 下(`operations\` 放行类、`interceptions\` 拦截类,见第 10 节),所以它同样在安装目录之外、覆盖安装不会丢。
|
||||
|
||||
### 3.4 数据放在哪里(升级/卸载都不会删)
|
||||
|
||||
覆盖安装时安装程序会**先静默运行旧版卸载器**,而卸载器会把整个安装目录删掉,所以数据库、规则、用户、上传的图片与字体都不能放在安装目录里。现在的规则:
|
||||
@@ -108,6 +110,7 @@ deploy\build.bat
|
||||
| 直接双击 `scoreboard.exe`(不经 Console) | 服务端默认行为不变:用工作目录下的 `data\`,**这种用法在覆盖安装时仍会丢数据** |
|
||||
|
||||
- Console 启动时会把这个目录用 `-data` 参数交给服务端,界面上方「运行日志」里能看到 `数据目录:...` 这一行;
|
||||
- **服务端日志也在数据目录下**:`<数据目录>\logs\` 里分 `operations\`(放行类)与 `interceptions\`(拦截类)两个子目录,按天 + 按大小轮转、自动清理旧文件,可用 `config.yaml` 的 `log:` 段改位置与保留份数(见第 10 节)。它在安装目录之外,覆盖安装不会丢;
|
||||
- **上传资源随赛事项目走**:赛事会标、大屏背景图、单位 logo,以及「赛事信息 → 字体」里上传的**字体文件**,都在 `<数据目录>\event_projs\<项目名>\uploads\` 里(字体在 `uploads\fonts\`)。它们是文件而不是数据库记录,**不在 `backups\` 的 `.db` 快照里**,备份 / 迁移时要连这个目录一起拷(见 8.5、8.6);
|
||||
- **一次性迁移**:如果新数据目录还是空的,而旧的 `<安装目录>\data` 里有数据,Console 会在启动时把旧目录**整体改名**过去(同盘很快),日志里会写「已把旧数据从 X 迁移到 Y」。任何失败(跨盘、权限不足等)都只记日志,旧数据原地保留,不会被删;
|
||||
- 如果两个目录里**都有数据**,Console 不做任何动作,只写一条警告日志,由现场人工决定保留哪一份;
|
||||
@@ -123,7 +126,7 @@ deploy\build.bat
|
||||
| 内容 | 实际生效的位置 | 管什么 |
|
||||
| --- | --- | --- |
|
||||
| Console 配置 | `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` | 端口、选屏策略、开机自启等(见 6.2) |
|
||||
| 服务端配置 | `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` | 监听地址/端口、备份间隔、日志级别(见 6.1) |
|
||||
| 服务端配置 | `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` | 监听地址/端口、备份间隔、日志级别与日志目录(见 6.1) |
|
||||
| 规则目录 | `%LOCALAPPDATA%\ScoreboardConsole\rules\` | 规则包 YAML;管理面板「导出 → 规则包 → 选择 YAML」时从这里选 |
|
||||
|
||||
**怎么改**:直接编辑上表里的文件。`config.json` 与 `config.yaml` 都是 Console 启动时读一次,改完要**退出并重启 Console**(托盘 → 退出);服务管理窗口的「配置文件」一行显示的就是 `console\config.json` 的实际路径,可用来确认。`rules\` 只是规则包存放目录,往里放文件不需要重启,但规则要在管理面板「导出 → 规则包 → 选择 YAML → 预览差异 → 确认导入并全量重算」里导入后才生效(服务端不会自动加载)。安装目录里那三份是模板,改了不生效。
|
||||
@@ -141,7 +144,7 @@ deploy\build.bat
|
||||
|
||||
> 从老版本覆盖升级上来的第一次启动:安装目录里那三份在覆盖安装时**就已经被旧卸载器删过一遍**(新装进去的是新版模板),Console 只能把模板复制到用户目录,升级前改在安装目录里的配置需要重做一遍。**从本版本起**(也就是改动落在上表位置之后),配置与规则才会跨升级保留。
|
||||
|
||||
> 日志是例外:它留在安装目录的 `logs\console.log`(每次运行都会重写的排查材料,不值得搬),所以卸载后日志会消失,配置与规则不会。
|
||||
> 日志分两处,别找错:Console 自己的排查日志留在安装目录的 `logs\console.log`(每次运行都会重写,不值得搬,卸载后会消失);**服务端的运行 / 放行 / 拦截日志在数据目录的 `logs\` 下**(`operations\` 与 `interceptions\` 两类,随数据目录跨升级保留,见第 10 节)。
|
||||
|
||||
首次启动后看 Console「运行日志」里这几行核对:
|
||||
|
||||
@@ -266,7 +269,10 @@ scoreboard.exe passwd -config "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" -da
|
||||
| `server.host` | `0.0.0.0` | 监听地址;`0.0.0.0` 表示局域网内其他设备也能访问 |
|
||||
| `server.port` | `19540` | HTTP 端口 |
|
||||
| `dataDir` | `data` | 数据目录,相对服务端工作目录解析;**Console 启动时会用 `-data <绝对路径>` 覆盖它**,配置项本身只对直接运行 `scoreboard.exe` 时生效 |
|
||||
| `log.level` | `info` | 日志级别:`debug` / `info` / `warn` / `error` |
|
||||
| `log.level` | `info` | 控制台与 `operations\` 放行类日志的采集级别:`debug` / `info` / `warn` / `error`;拦截类日志固定按 `debug` 采集,不受它影响(见第 10 节) |
|
||||
| `log.dir` | 空 | 日志根目录(其下分 `operations\` / `interceptions\`);留空 = `<数据目录>\logs` |
|
||||
| `log.maxSizeMB` | `20` | 单个日志文件大小上限(MB);`<=0` 用内置默认 |
|
||||
| `log.keepFiles` | `10` | 每类日志保留份数(含当前文件);`<=0` 用内置默认 |
|
||||
| `backup.intervalMinutes` | `5` | 自动备份间隔(分钟),`<=0` 关闭自动备份 |
|
||||
| `backup.keep` | `20` | 每个库滚动保留的备份份数 |
|
||||
| `server.allowedOrigins` | `[]` | 额外放行的 WebSocket Origin 白名单;默认只放行同源与不带 Origin 的客户端 |
|
||||
@@ -583,12 +589,69 @@ http://<主机IP>:19540/display
|
||||
|
||||
---
|
||||
|
||||
## 10. 上线检查清单
|
||||
## 10. 服务端日志与拦截复盘
|
||||
|
||||
服务端会把日志写到**数据目录**下的 `logs\`(默认 `%LOCALAPPDATA%\ScoreboardConsole\data\logs\`),按用途分成**两类**目录,各自按天 + 按大小轮转:
|
||||
|
||||
```
|
||||
<数据目录>\logs\
|
||||
├── operations\ # 放行类:放行的写操作 + 常规运行日志(启动、迁移…)
|
||||
└── interceptions\ # 拦截类:我们主动拦截的报错 + 服务端异常
|
||||
```
|
||||
|
||||
> 这与 Console 自己的 `logs\console.log`(在安装目录里,只装 Console 与服务端的控制台输出)不是一回事:要复盘「某次操作为什么被拒绝」,看的是数据目录 `logs\interceptions\`。
|
||||
|
||||
### 10.1 两类日志各记什么
|
||||
|
||||
| 目录 | 内容 | 采集级别 |
|
||||
| --- | --- | --- |
|
||||
| `operations\` | **放行类**:放行的写操作(`操作已放行`,含 action / 实体 / 操作人 / requestId)+ 常规运行日志(启动、数据库迁移…) | 按 `log.level` 过滤(默认 `info`) |
|
||||
| `interceptions\` | **拦截类**:我们主动拦截的报错(WARN,含中文原因 + 拦截点 `文件:行 函数名` + method/path + 操作人/角色 + requestId)+ 拦截上下文(DEBUG,含 detail 与来源 IP)+ **服务端异常(ERROR)** | **固定按 `debug` 采集,不受 `log.level` 影响** |
|
||||
|
||||
拦截类固定采集是刻意的:默认级别是 `info`,跟着过滤就看不到现场最需要的那份详细拦截日志。中间件层的拦截(未登录 401 / 无权限 403 / CSRF 校验失败 / 未改初始密码 / 操作过于频繁 429)同样进这里,一样带中文原因与拦截点。
|
||||
|
||||
### 10.2 文件命名与轮转
|
||||
|
||||
- 文件名按天:`operations-YYYYMMDD.log`、`interceptions-YYYYMMDD.log`;同一天写满一个文件后接着写 `.1`、`.2`…,例如 `interceptions-20260911.1.log`;
|
||||
- **保留份数含当前文件**,超出后自动删最旧的(每开一个新文件都会清理一遍);
|
||||
- 单文件大小上限 `log.maxSizeMB`(默认 20 MB),每类保留份数 `log.keepFiles`(默认 10)。两类合计最多约 `2 × keepFiles × maxSizeMB`,默认约 400 MB,正常跑一整天不会把盘写满;
|
||||
- 万一日志目录不可写(权限、盘满),服务端只把告警打一次,之后降级为只写控制台,**不影响业务请求**。
|
||||
|
||||
### 10.3 配置(`config.yaml` 的 `log:` 段)
|
||||
|
||||
| 项 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `log.level` | `info` | 控制台与 `operations\` 的采集级别:`debug` / `info` / `warn` / `error`;**拦截类不受它影响** |
|
||||
| `log.dir` | 空 | 日志根目录,其下分 `operations\` 与 `interceptions\`;留空 = `<数据目录>\logs` |
|
||||
| `log.maxSizeMB` | `20` | 单个日志文件大小上限(MB);`<=0` 用内置默认 |
|
||||
| `log.keepFiles` | `10` | 每类保留份数(含当前文件);`<=0` 用内置默认 |
|
||||
|
||||
`dir` / `maxSizeMB` / `keepFiles` 三个键都有默认值,不配也能跑;仓库根的 `config.yaml` 里已带注释,照抄即可。改完要重启 Console(配置只在启动时读一次,见 3.5)。
|
||||
|
||||
### 10.4 现场复盘:某次操作被拒了怎么看
|
||||
|
||||
1. 打开 `<数据目录>\logs\interceptions\`,找当天的 `interceptions-YYYYMMDD.log`(有续写文件时按 `.1`、`.2` 往后看);
|
||||
2. 搜 `请求被拦截`(WARN)那一行,它带:
|
||||
- `reason`:中文原因(与页面上看到的提示同源);
|
||||
- `origin`:**拦截点**,形如 `internal/httpapi/repo_events.go:231 normalizeAvgMode`——直接告诉你是哪个函数的哪一行拦下的;
|
||||
- `code` / `method` / `path`:错误码与请求地址;
|
||||
- `actor`(`用户名#id`,未登录记 `guest`)/ `role` / `requestId`;
|
||||
3. 想定位得更准:在浏览器开发者工具的 **Network** 面板点开那条失败请求,复制响应头 `X-Request-Id`,在日志文件里搜这个值;同值的 DEBUG 行「拦截上下文」会再补上 `detail` 与来源 IP;
|
||||
4. 想对照同一时间有没有成功的操作,去 `operations\` 里搜同一个 `requestId`(放行日志带 action / 实体 / 操作人)。
|
||||
|
||||
> 导入类操作的行级报错也留痕:`导入行被拦截`(DEBUG)会记第几行、原因与拦截点。日志不记请求体,证件号这类个人信息不会因为日志多一处泄露面;`detail` 字段与页面响应同源。
|
||||
|
||||
> 服务端自身异常(5xx)也在 `interceptions\` 里,是 `请求失败`(ERROR)并同样带 `origin`。
|
||||
|
||||
---
|
||||
|
||||
## 11. 上线检查清单
|
||||
|
||||
- [ ] 服务端开机自启、崩溃自动拉起已验证
|
||||
- [ ] 「运行日志」里的 `数据目录:` 指向 `%LOCALAPPDATA%\ScoreboardConsole\data`(不是安装目录),且没有迁移警告
|
||||
- [ ] 「运行日志」里的 `用户数据目录:` 指向 `%LOCALAPPDATA%\ScoreboardConsole`,且没有「准备 … 失败」警告(配置与规则也在用户目录里,见 3.5)
|
||||
- [ ] 数据目录的 `backups\` 可写,已生成至少一份备份
|
||||
- [ ] **服务端日志目录可写**:数据目录下已出现 `logs\operations\` 与 `logs\interceptions\` 两个目录(见第 10 节);知道操作被拒时去 `interceptions\` 搜原因与拦截点
|
||||
- [ ] 已把数据目录整个拷一份到 U 盘(含 `event_projs\<项目名>\uploads\` 里的图片与字体)
|
||||
- [ ] **当前赛事正确**:「导出 → 赛事项目包 → 赛事项目列表」里带「当前」徽标的是本次要用的那个项目(刚导入的包要切换过去才算生效,见 8.5)
|
||||
- [ ] 大屏 HDMI 输出定位正确、分辨率正确、字体正常
|
||||
|
||||
Reference in new issue
Block a user