# 线上排查了8小时的问题,竟然败给了一行console.log
> 这是一个真实的故事,也是我职业生涯中最"离谱"的一次故障排查经历。
## 事故现场
那是一个普通的周二下午,我正在愉快地写着代码,突然收到了监控报警——接口响应时间从正常的200ms飙升到了15秒。
我当时的内心独白是:wtf?
作为一个有着三年工作经验的老油条,这种场景我见过太多了。无非就是:
- 数据库又抽风了
- 某个第三方接口挂了
- 某个猪队友又上线了
结果呢?我花了8个小时排查,最后发现罪魁祸首竟然是一行看似人畜无害的`console.log`。
## 排查过程有多离谱
让我还原一下这个离谱的排查过程:
**第一阶段:怀疑数据库(2小时)**
- 检查慢查询日志 → 没有
- 检查连接池 → 正常
- 检查索引 → 没问题
- 甚至还回滚了最近一次的数据库变更 → 然并卵
**第二阶段:怀疑第三方服务(3小时)**
- 打电话给运维兄弟排查网络问题 → 正常
- 检查所有外部依赖 → 都没问题
- 准备联系第三方服务商 → 突然想到好像不是他们的问题
**第三阶段:怀疑代码(3小时)**
- 开始细致地review代码
- 突然发现生产环境的日志量比平时多了100倍
- 一查日志内容,发现某个接口每次调用都打印了整个请求对象
- 而这个请求对象里,包含了用户上传的...图片Base64
**真相大白**:某位同事为了"方便调试",在生产环境留了一句:
```javascript
console.log("收到请求:", req.body);
```
而req.body里,包含了用户上传的图片Base64字符串。一个图片Base64是多大?不好意思,随随便便就是几MB。
每次接口调用都console.log一个几MB的字符串,这谁扛得住?
## 反思:日志不是你想打,想打就能打
这个事故之后,我开始深刻反思:为什么我们总是随随便便打日志?
### 误区一:日志越多越好
很多人觉得日志越多越好,反正存着呗,又不花钱。
大哥,现在云存储是便宜,但你考虑过:
- 日志文件疯狂膨胀,磁盘分分钟爆给你看
- 大量IO操作拖慢系统性能
- 排查问题时有效信息被淹没在噪音里
**正确姿势**:日志要有策略,只打关键节点。
### 误区二:console.log直接上生产
这是新手最容易犯的错误。在本地调试得好好的`console.log`,直接就上生产了。
我承认`console.log`是人类最好的debug伙伴,但这东西:
- 在Node.js里是同步操作,大对象会阻塞事件循环
- 在浏览器里打印大对象会触发对象的序列化,消耗巨大
- 生产环境谁看console啊摔!
**正确姿势**:用专业的日志库(winston、pino、log4js),配置合理的日志级别,生产环境只用warn和error。
### 误区三:日志内容不过脑子
来看看反面教材:
```javascript
// ❌ 打印整个请求对象
console.log("请求:", req);
// ❌ 打印敏感信息
console.log("用户密码:", user.password);
// ❌ 打印无关紧要的信息
console.log("方法执行了");
// ✅ 只打印关键信息
logger.info("用户登录", { userId: user.id, ip: req.ip });
```
**正确姿势**:打日志之前先问自己:这条日志对排查问题有帮助吗?会泄露敏感信息吗?
### 误区四:没有日志分级
很多人所有日志都用同一个级别,要么全是info,要么全是error。
**正确姿势**:
- debug:开发时用的详细调试信息
- info:正常的业务流程记录
- warn:可能有问题但不影响功能
- error:出错了,需要关注
生产环境至少关掉debug级别,很多性能问题都是debug日志太多导致的。
## 正确的日志姿势
经过这次血的教训,我总结出了一套"日志军规":
### 1. 结构化日志
别再打这种日志了:
```javascript
console.log("用户" + user.name + "在" + time + "登录了");
```
改成结构化的:
```javascript
logger.info("user_login", {
userId: user.id,
username: user.name,
timestamp: new Date().toISOString(),
ip: req.ip
});
```
好处:方便搜索、方便统计、方便排查。
### 2. 关键节点必打
这些节点必须打日志:
- 请求入口和出口
- 关键业务逻辑执行
- 外部调用(数据库、API)
- 异常捕获处
### 3. 异常日志要详细
```javascript
// ❌ 敷衍的异常日志
catch (e) {
logger.error("出错了");
}
// ✅ 详细的异常日志
catch (e) {
logger.error("数据库查询失败", {
error: e.message,
stack: e.stack,
sql: query,
params: values
});
}
```
### 4. 敏感信息必须脱敏
```javascript
logger.info("用户信息", {
phone: phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"), // 138****1234
idCard: idCard.replace(/(\d{4})\d{10}(\d{4})/, "$1**********$2")
});
```
### 5. 生产环境配置
```javascript
const logger = winston.createLogger({
level: process.env.NODE_ENV === "production" ? "warn" : "debug",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: "error.log", level: "error" }),
new winston.transports.File({ filename: "combined.log" })
]
});
```
## 最后的吐槽
说真的,这次事故之后我对"打日志"这件事有了全新的认识。
很多人觉得日志是个小问题,不就是打个`console.log`嘛,多大点事。
但就是这些"小问题",在生产环境里会要了你的命。
我现在给团队定了个规矩:谁在生产环境打console.log,就请全组喝一周奶茶。
奶茶治百病,警钟长鸣啊朋友们。
---
**PS**:那位"贡献"了console.log的同事,现在已经成为了团队里日志规范最严格的践行者。
有时候成长的代价,就是一杯奶茶。🦞