3. 常见日志缺失原因分析
日志丢了,别急着骂中间件。
我排查过上百个日志缺失的案子,九成以上都出在下面这四个坑里。这四种原因听着简单,但每次出现都带着不同的面具。咱们一个个过。
3.1 代码没打日志
最直白的原因——压根没写。
别笑。我见过不少线上服务,核心业务逻辑几十行,一句 print 都没有。开发时靠 IDE 断点调试,上线后两眼一抹黑。
为什么会这样?
- 赶进度——功能优先,日志后补,然后就没有然后了。
- 觉得没必要——“这个分支肯定不会进”,结果生产上触发了。
- 日志打在错误级别——用了
logger.debug,生产配置只开INFO,等于没打。
我的习惯:每写一个 if 分支,至少打一行日志。异常捕获必须打印堆栈。别偷懒,生产环境不会给你第二个机会。
看个典型例子:
// 问题代码:安静地吞异常
try {
process(data);
} catch (Exception e) {
// 啥也不打印,连堆栈都不留
}
// 正确做法:
try {
process(data);
} catch (Exception e) {
log.error("数据处理失败,dataId={}", data.getId(), e); // 第三个参数传异常!
}
我曾在项目中遇到一个诡异的内存泄漏,查了两天,最后发现某个异步回调里 catch 了异常但没打任何日志。线上症状就是偶尔丢消息,根本无从下手。
3.2 缓冲区溢出
日志打好了,但输出通道满了,写不进去。
缓冲区溢出的本质:生产者(业务线程)比消费者(日志写盘/网络发送)快太多。队列满了之后,常见的处理策略是丢弃——日志就丢了。
| 框架/组件 | 溢出策略 | 后果 |
|---|---|---|
| Logback AsyncAppender | 丢弃最旧 / 丢弃当前 | 10% ~ 30% 日志丢失 |
| Java Util Logging | 阻塞写入 | 业务线程卡死 |
| Syslog (UDP) | 静默丢弃 | 无反馈,最坑 |
| 自定义内存队列 | offer 超时丢弃 | 日志“假成功” |
你应该关注两个指标:队列积压率和丢弃计数器。Logback 的 DiscardingPolicy 会打印一条 WARN,但很多人根本没开监控看。
我曾经踩过的坑:给一个高并发服务配了异步日志,队列大小设为 256。压测发现业务延迟正常,但线上始终缺日志。后来一查,每秒产生 2000 条日志,队列被塞满后疯狂丢弃。恢复队列后,写入线程又成了瓶颈——磁盘 IO 扛不住。
教训:异步日志不是银弹。必须结合流量压测来配队列长度和丢弃策略。
3.3 磁盘满了
日志写磁盘,磁盘满了,日志自然写不进去。这是最“物理”的原因。
但有趣的是,很多时候不是日志所在的磁盘满了,而是其他进程把空间占了。比如临时文件、Docker 容器日志、core dump 文件。
怎么快速定位?
- 执行
df -h看各分区使用率。 - 用
lsof | grep deleted | grep log找已经被删但进程还占着句柄的文件——这种情况常见于日志轮转后没重启应用。 - 检查
/var/log下是否有messages或syslog的 .gz 归档没清理。
我参与过一次事故复盘:某个监控系统突然不报日志了,查了两小时,最后发现 / 分区 100%。罪魁祸首是 Docker overlay2 目录里残留的容器日志,一个容器攒了 20GB。你想想看,日志框架尝试写文件时返回 ENOSPC,很多框架的处理方式很粗暴——直接放弃写,连错误都不往外抛。
预防建议:
- 日志单独挂盘,比如
/data/logs,和系统盘分离。 - 设置磁盘水位告警,80% 通知,95% 紧急。
- 使用
logrotate并配置maxage或maxsize,保证历史日志不撑爆。
3.4 网络丢包
日志要发到远端(Kafka、ELK、Syslog 服务器)时,网络链路本身不可靠。
UDP 传输最常见也最惨。Syslog (UDP)、某些 metrics agent 默认用 UDP,丢包了发送端完全不知道。TCP 会好一些,但连接中断、重连期间的日志也会丢——尤其是在长连接频繁重建、缓冲区又没持久化的时候。
我曾经遇到一个典型场景:
- 应用打印日志到本地文件,同时用 Filebeat 发到 Logstash。
- 某次网络抖动,Filebeat 和 Logstash 之间的 TCP 连接断了。Filebeat 的 registry 文件记录了读取偏移量,但连接恢复后,重传期间新产生的日志写入本地文件,Filebeat 还没来得及读——这段时间的日志就没有被发送。
- 表面上看 Filebeat 有重传机制,但实际丢包窗口只有 1~2 秒,却足以漏掉关键错误。
解决方案不是单一的:
| 方案 | 优缺点 |
|---|---|
| TCP + ACK 确认 | 可靠,但写放大,延迟增加 |
| 本地文件 + 审计监控 | 本地不丢,但采集端可能丢;需要比对文件内行号 |
| 消息队列 + 持久化 | 最可靠,但架构复杂 |
| 数据完整性校验 | 接收端对每批日志计算哈希,发送端重传缺失批次 |
我个人习惯:日志链路设计时遵循“本地优先”原则。所有关键日志先写本地磁盘,再考虑远程传输。远程传输如果丢包了,至少本地还有一份兜底数据。事后回溯可以直接从本地文件恢复。
原因总结与排查路径
下面这张图梳理了四种缺失原因以及对应的排查线索,你可以把它当作快速诊断手册。
每次遇到日志缺失,别急着怀疑环境。先问问自己:代码里是不是忘了打?队列是不是被塞满了?磁盘是不是爆了?网络是不是飘了?按这个顺序查,九成的问题在十分钟内就能定位。
今天这四种原因在实际项目中往往叠加出现。缓冲区溢出可能是瞬间流量导致,磁盘满可能是另一个日志轮转配置的锅,网络丢包又可能暴露了代码根本没重试。这也是为什么我强调先用低成本手段排除物理原因,再深入代码。