Node.js 实战六:日志系统设计 —— 不只是 console.log,而是可追溯的行为记录链
开发时你写:
console.log('进来了');
上线后出 bug,产品问:
“这个用户为啥订单创建失败?”
你打开服务器一看,啥也没有。于是陷入盲猜、靠运气 debug 的地狱模式。
你需要日志。不是调试用的,而是生产可追溯的。
本篇我们讲讲:
如何在 Node.js 项目中构建一个 结构清晰、功能全面、适合生产环境的日志系统。
一、日志系统需要满足哪些核心能力?
能力 | 含义 |
---|---|
分级分类 | info、warn、error、debug,不同级别记录 |
可持久化 | 存磁盘、发远程、进数据库 |
可筛查 | 带时间、用户、路径、traceId 等信息 |
可格式化 | JSON、文本、彩色 console |
可扩展 | 接入监控系统,发送告警,支持日志聚合服务 |
二、日志库推荐:winston / pino
winston(功能全、文档好、插件多)
npm install winston
import winston from 'winston';const logger = winston.createLogger({level: 'info',format: winston.format.combine(winston.format.timestamp(),winston.format.json()),transports: [new winston.transports.Console(), // 控制台new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),new winston.transports.File({ filename: 'logs/combined.log' }),],
});
三、日志等级与使用规范
等级 | 用于记录 |
---|---|
error | 程序异常、业务错误 |
warn | 潜在问题、预期外但非致命事件 |
info | 请求成功、关键行为记录 |
debug | 调试信息,开发/测试环境开启 |
使用建议:
logger.info('User login success', { userId: 123 });
logger.error('Create order failed', { error: e.message, reqId });
四、给每个请求打上 traceId(链路追踪)
为了把一串相关日志串起来:
-
在中间件中生成唯一 ID(如 uuid)
-
注入到 ctx/request 中,写入每条日志
const { v4: uuidv4 } = require('uuid');app.use(async (ctx, next) => {ctx.traceId = uuidv4();await next();
});
然后写日志时统一带上:
logger.info('Processing payment', { traceId: ctx.traceId });
这样你就能一眼查出“这一波请求发生了啥”。
五、日志落盘与分环境配置
推荐结构:
logs/
├── error.log
├── combined.log
├── access.log
开发环境只打印 console
生产环境写文件 + 控制台(可彩色输出)
if (process.env.NODE_ENV !== 'production') {logger.add(new winston.transports.Console({format: winston.format.simple()}));
}
六、接入远程日志系统(推荐方式)
目标系统 | 工具 |
---|---|
阿里云 SLS | aliyun-sls-sdk |
ElasticSearch | filebeat + logstash |
Loki + Grafana | winston-loki 插件 |
日志告警(如钉钉) | winston-dingding 等 webhook 插件 |
建议:关键 error 日志可额外推送报警渠道
七、扩展:用户行为日志 / 操作记录系统设计
除了系统层级的 error/info/debug,你还应该记录 业务行为日志,例如:
-
谁登录了系统?
-
谁删除了某条数据?
-
谁修改了用户权限?
-
谁导出了哪些数据?
这些信息不仅有助于:
-
审计追责
-
用户行为分析
-
风控与安全预警
-
后台展示操作记录
建议做法:
-
定义一个 log_behavior 表,结构如:
CREATE TABLE log_behavior (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT,action VARCHAR(64),target_type VARCHAR(64),target_id BIGINT,timestamp DATETIME,detail TEXT
);
2. 在关键业务逻辑中插入行为日志:
await behaviorLogger.log({userId: ctx.state.user.id,action: 'delete_user',targetType: 'user',targetId: deletedUser.id,detail: JSON.stringify({ reason: 'admin operation' }),
});
3. 可配合后台系统展示为“操作日志模块”。
八、日志系统落地注意事项清单
项 | 建议做法 |
---|---|
❯ 文件命名 | 按日期拆分,或按级别分文件(logs/error-20240512.log) |
❯ 格式 | JSON 格式日志利于机器读取(如 ELK) |
❯ 打印内容 | 包含 userId、traceId、req.path、IP、UA 等上下文信息 |
❯ 保留期限 | 每日日志建议保留 7~30 天,可用定时清理脚本 |
❯ 安全性 | 过滤敏感字段,如密码、token,不应打印入日志 |
总结
日志不是“写给自己看的调试输出”,它是:
-
问题追溯的核心线索
-
用户行为的安全记录
-
系统稳定的前置保障
-
可观测性的最底层能力
稳定的项目,不是靠程序没出 bug,而是出了 bug 你“知道它在哪、为什么、什么时候发生的”。
构建一套规范、分级、可扩展的日志系统,是任何线上项目的“标配基建”。