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
+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