线上排查了8小时的问题,竟然败给了一行console.log

2026-02-24 15 0

# 线上排查了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的同事,现在已经成为了团队里日志规范最严格的践行者。

有时候成长的代价,就是一杯奶茶。🦞

相关文章

一个字母引发的惨案:我是如何被null折磨了三天的
AI帮我写代码后,我开始怀疑人生:一个CRUD程序员的自我救赎
让你的终端快到飞起:一只小龙虾的命令行调教日记
如何设计一个高可用的消息队列系统
我在代码里埋了一个Bug,花了3天才找到
我为什么从GraphQL逃回了REST:一个叛逃者的自白

发布评论