Files
web-table/docs/superpowers/specs/2026-06-24-structured-logging-design.md

114 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 结构化日志系统(稳定性诊断)设计
- 日期2026-06-24
- 状态:设计(已口头确认,待实现)
- 范围v1 聚焦**稳定性诊断**——捕获错误与崩溃(尤其 `/api/export-excel` 的 OOM 路径)。
## 1. 背景与问题
当前服务端只有 3 处 ad-hoc `console.error/warn`,且只记 `err.message`**丢失堆栈**)。
输出被 NSSM 原样灌进 `logs/stdout.log`/`stderr.log`,追加、不轮转——导致崩溃堆栈堆积、不可检索。
诊断 export 的 OOM单次全量导出峰值 ~1.3GB 堆)时,缺少"哪次导出、多少行、耗时多久"的现场。
约束114 不通外网 → 自包含文件日志,排除 SaaS单 Node 进程、内部工具、低流量。
## 2. 目标
在不改动业务逻辑的前提下,新增一层**结构化、分级、同步落盘、自带轮转**的应用日志,
让"出错/崩溃时能查到完整现场"。
## 3. 非目标YAGNI
- 不做请求级全量审计 / 用户行为追踪(属"运行可见性/审计",非 v1
- 不做客户端(浏览器)错误采集。
- 不做集中式日志聚合ELK 等)。
- 不重构 `dbConfig` 两处重复(不属于本次范围)。
## 4. 方案
### 4.1 选型log4js `fileSync`
- **`fileSync` appender 同步写**`fs.appendFileSync`)→ 进程 OOM/被杀前日志已落盘,满足崩溃诊断刚需。
- 内置按大小轮转(`maxLogSize` + `backups`),无需手写。
- 分级、category、pattern layout成熟轻量。
- 引入依赖 `log4js`(约 ~3 个小间接依赖)。
### 4.2 分层
```
应用日志(新增) log4js fileSync → logs/app/app.log同步 + 10MB×5 轮转) ← 查错误/崩溃
框架/致命日志 NSSM 的 stdout/stderrNext 启动、V8 崩溃栈) ← 兜底,保持不动
```
### 4.3 logger 模块 `src/server/logger.ts`
```ts
import log4js from "log4js";
log4js.configure({
appenders: {
app: {
type: "fileSync",
filename: "logs/app/app.log",
maxLogSize: 10 * 1024 * 1024, // 10MB
backups: 5,
layout: { type: "pattern", pattern: "[%d{ISO8601}] [%p] [%c] %m" },
},
},
categories: { default: { appenders: ["app"], level: "info" } },
});
export const apiLogger = log4js.getLogger("api");
export const exportLogger = log4js.getLogger("export");
```
- `filename` 用相对路径,`next start`NSSM 的 `AppDirectory`/`next dev` 的 cwd 均为项目根,解析正确。
- 输出样例:
`[2026-06-24T10:30:01.123] [ERROR] [export] Excel export failed · rows=33142 · dur=18234ms · RangeError: Invalid string length\n at ...`
### 4.4 记什么(诊断聚焦,不给常规成功查询加噪)
| 事件 | 级别 | 字段 |
|----|----|----|
| production-data 查询失败 | ERROR | workshopNo、完整堆栈 |
| production-data 慢查询(>3s | WARN | workshopNo、耗时 |
| export 锁被并发占用429 | WARN | — |
| export 锁超时回收 | WARN | 取代现有 `console.warn` |
| export 开始 / 成功 | INFO | rows、bytes、耗时OOM 诊断核心线索) |
| export 失败 | ERROR | 完整堆栈、rows、耗时 |
| 进程启动 | INFO | logger ready |
要点:现有 3 处 `console.error/warn` **全部替换**为 logger 调用;`err.message` **升级为完整 `err.stack`**
### 4.5 路由改造
- `production-data/route.ts`catch 分支用 `apiLogger.error(...)` 带栈 + workshopNo查询耗时 >3s 时 `apiLogger.warn(...)`
- `export-excel/route.ts`:锁占用/回收 `exportLogger.warn`;开始记 `rows`、成功记 `rows/bytes/dur`、失败 `exportLogger.error` 带栈 + dur保留现有返回逻辑不变。
- 用一个纯函数 `formatError(err): string` 统一提取 `err.stack ?? String(err)`,便于单测。
### 4.6 涉及文件
| 文件 | 动作 |
|----|----|
| `src/server/logger.ts` | 新增log4js 配置 + 导出 logger + `formatError` |
| `src/app/api/production-data/route.ts` | 改:失败 ERROR带栈+ 慢查询 WARN |
| `src/app/api/export-excel/route.ts` | 改:锁/开始/成功/失败 全套日志(带栈) |
| `package.json` | +`log4js` 依赖 |
## 5. 边界情况
- **OOM 落盘**fileSync 同步写,每行即时 flush崩溃前已写入。
- **首次写入**log4js 自动创建 `logs/app/` 目录与文件。
- **轮转**:达到 `maxLogSize` 自动滚动为 `app.log.1..5`,更早的删除。
- **本地 dev vs 114 生产**:均写入项目根下 `logs/app/app.log`;不影响 NSSM 的 stdout/stderr。
## 6. 测试
- 纯函数 `formatError(err)`:用 vitest 单测Error→stack、非 Error→字符串、null/undefined 安全)。
- 路由日志为副作用,以**手验**为准:本地断开 DB 触发 production-data 失败 → 确认 `logs/app/app.log` 有 ERROR 带栈行;触发一次导出 → 确认 INFOrows/bytes/dur/ 失败 ERROR 行。
- 不引入 log4js 行为的单测(第三方,按项目惯例不测)。
## 7. 部署注意
新增 `log4js` 依赖114 部署须执行 `npm install`CLAUDE.local.md 第 3 步),再 `next build` + 重启。