5.0 KiB
5.0 KiB
结构化日志系统(稳定性诊断)设计
- 日期: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
fileSyncappender 同步写(fs.appendFileSync)→ 进程 OOM/被杀前日志已落盘,满足崩溃诊断刚需。- 内置按大小轮转(
maxLogSize+backups),无需手写。 - 分级、category、pattern layout,成熟轻量。
- 引入依赖
log4js(约 ~3 个小间接依赖)。
4.2 分层
应用日志(新增) log4js fileSync → logs/app/app.log(同步 + 10MB×5 轮转) ← 查错误/崩溃
框架/致命日志 NSSM 的 stdout/stderr(Next 启动、V8 崩溃栈) ← 兜底,保持不动
4.3 logger 模块 src/server/logger.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 带栈行;触发一次导出 → 确认 INFO(rows/bytes/dur)/ 失败 ERROR 行。 - 不引入 log4js 行为的单测(第三方,按项目惯例不测)。
7. 部署注意
新增 log4js 依赖,114 部署须执行 npm install(CLAUDE.local.md 第 3 步),再 next build + 重启。