服务器磁盘一夜写满 100GB,根因是一行 DEBUG 日志
凌晨 3 点,服务器磁盘使用率突然达到 100%。
接口开始报错,日志写不进去,应用重启也失败。
最后发现,不是文件上传,也不是数据库备份,而是一行打印完整请求体的
DEBUG日志,一晚上写了 100GB。
一、事故现场
凌晨告警连续响起:
磁盘使用率:72% -> 100% 接口错误率:0.2% -> 18% 日志写入:No space left on device 应用重启:失败登录服务器后,第一条命令:
df-h结果显示:
Filesystem Size Used Avail Use% Mounted on /dev/vdb1 200G 200G 0 100% /data磁盘确实满了。
应用还没有完全停止,但已经出现各种看起来毫不相关的异常:
日志无法写入 临时文件创建失败 上传接口失败 定时任务执行异常 应用无法生成新的 PID 文件磁盘写满后,最危险的地方就在这里:
它不会只影响日志,而是可能让整台机器上的多个服务一起异常。
二、先分清:空间满了,还是 inode 用完了
df -h查看的是磁盘空间。
还要检查 inode:
df-i如果磁盘容量还有很多,但 inode 使用率已经 100%,通常是产生了海量小文件。
这次的情况比较直接:容量已经耗尽,inode 正常。
下一步从挂载点开始找最大目录:
du-xhd1/data|sort-h很快发现:
3.2G /data/upload 8.7G /data/backup 176G /data/logs继续向下查:
du-xhd1/data/logs|sort-h最终定位到订单服务:
168G /data/logs/order-service查找最大的日志文件:
find/data/logs/order-service-typef-size+1G-printf'%s %p\n'\|sort-nr\|head-20其中当天的app.log已经达到 103GB。
三、100GB 日志到底写了什么?
不要直接打开整个文件。
先查看末尾少量内容:
tail-n100/data/logs/order-service/app.log屏幕上反复出现同一种日志:
DEBUG c.example.order.CallbackService callback request: {"orderId":"...","items":[...],"ext":{...}}每一条都打印了完整回调请求。
请求里包含商品明细、营销信息和扩展字段,平均一条大约 6KB。
高峰期回调 QPS 接近 200:
6KB × 200 × 86400 ≈ 103GB/天100GB 不是某一刻突然产生的。
它只是以每秒 1MB 多一点的速度,安静地写了一整天。
最终定位到代码:
log.debug("callback request: {}",objectMapper.writeValueAsString(request));这行代码已经存在一段时间,以前却没有出事。
真正让它爆炸的是当天的一次配置变更:
logging:level:com.example.order:DEBUG为了临时排查一个回调问题,有人把整个订单包的日志级别调整成了DEBUG。
问题处理完后,配置没有恢复。
同时日志只按天切分,没有限制单个文件大小,也没有限制历史日志总量。
三个条件叠加,最终把磁盘写满:
生产误开 DEBUG ↓ 每个请求打印完整 JSON ↓ 高峰期每秒写入约 1.2MB ↓ 日志没有大小和总量上限 ↓ 磁盘使用率达到 100% ↓ 应用和同机服务陆续异常四、为什么删了日志,磁盘空间还没回来?
事故处理中,有人第一时间执行:
rmapp.log文件在目录中消失了,但df -h仍然显示磁盘 100%。
这是因为 Java 进程还持有这个文件的打开句柄。
在 Linux 中,目录项被删除,不代表正在使用该文件的进程已经释放空间。
可以检查已删除但仍被进程占用的文件:
lsof+L1可能看到:
java 18472 app 217w REG 253,17 103G 0 /data/logs/order-service/app.log (deleted)此时需要让进程关闭对应文件描述符,例如在完成止损和流量处理后,正常重启应用。
如果业务暂时不能重启,也可以评估使用truncate清空当前日志:
truncate-s0/data/logs/order-service/app.log但这会直接丢失日志,执行前必须:
确认目标文件绝对路径 保存必要的故障样本 确认日志是否仍有审计价值 确认没有选错服务或文件线上不要使用模糊通配符批量删除,更不要为了腾空间随意清理数据库、上传文件和未知目录。
五、事故发生时,正确的止损顺序
这类事故不能只做“删日志”一个动作。
我更建议按照下面的顺序处理。
1. 先停止继续写爆
把对应包的日志级别恢复到INFO,或者临时关闭问题日志。
如果通过配置中心动态调整,需要确认配置已经在所有实例生效。
2. 保留必要证据
保存:
问题日志样本 配置变更记录 磁盘监控曲线 异常开始时间 日志增长速度否则空间恢复后,很难还原根因。
3. 清理确定可以删除的文件
优先处理已经归档、确定过期的日志。
对于正在写入的文件,先确认进程句柄,避免出现“文件删了,空间仍未释放”。
4. 确认应用恢复
至少检查:
df-hdf-i同时观察接口成功率、应用日志、进程状态和同机服务。
5. 再处理永久修复
应急清理只是让服务暂时恢复。
不修改日志代码、滚动策略和告警,第二天磁盘还会再次写满。
六、第一处修复:不要打印完整请求体
原来的代码:
log.debug("callback request: {}",objectMapper.writeValueAsString(request));至少存在四个问题:
请求体可能很大 序列化动作已经提前执行 请求中可能包含敏感信息 高 QPS 下日志量会被成倍放大更合理的是只记录定位问题需要的字段:
log.info("payment callback, requestId={}, orderId={}, itemCount={}",request.getRequestId(),request.getOrderId(),request.getItems().size());如果完整报文确实需要保留,也应该考虑:
字段脱敏 限制最大长度 按比例采样 只在异常时记录必要内容 写入专门的审计存储 设置明确保留期限日志不是数据备份系统,也不应该承担完整业务报文存档。
七、第二处修复:同时限制文件大小、保留时间和总量
只配置“每天切一个文件”并不够。
如果一天产生 100GB,午夜之前它仍然是一个 100GB 文件。
使用 Spring Boot 默认 Logback 时,可以根据项目版本配置类似策略:
logging:file:name:/data/logs/order-service/app.loglevel:root:INFOcom.example.order:INFOlogback:rollingpolicy:file-name-pattern:/data/logs/order-service/app.%d{yyyy-MM-dd}.%i.log.gzmax-file-size:200MBmax-history:14total-size-cap:5GBclean-history-on-start:true这几个参数解决不同问题:
max-file-size:限制单个日志文件 max-history:限制保留周期 total-size-cap:限制归档日志总量 clean-history-on-start:应用启动时清理过期归档配置名称和默认值可能随 Spring Boot、Logback 版本变化,上线前应根据项目实际版本验证滚动和清理行为。
如果使用自定义logback-spring.xml,可以配置SizeAndTimeBasedRollingPolicy:
<appendername="FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>/data/logs/order-service/app.log</file><rollingPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>/data/logs/order-service/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern><maxFileSize>200MB</maxFileSize><maxHistory>14</maxHistory><totalSizeCap>5GB</totalSizeCap></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern></encoder></appender>maxHistory和totalSizeCap应该同时设置。
保留 14 天,并不意味着磁盘一定安全。如果每天产生 20GB,仍然可能保留数百 GB;总量上限才是最后一道边界。
八、第三处修复:不要等到 100% 才告警
磁盘告警至少应该分级:
70%:提醒,观察增长速度 80%:告警,开始定位大目录 90%:严重告警,立即处理除了使用率,还应该监控:
磁盘剩余空间 inode 使用率 每小时增长速度 日志目录大小 单个文件增长速度 应用日志写入失败次数如果磁盘从 70% 增长到 80% 用了一小时,就不能等它真正达到 90% 才处理。
增长速度往往比当前百分比更早暴露问题。
九、日志上线前检查清单
[ ] 生产环境没有误开 DEBUG 或 TRACE [ ] 不打印完整密码、Token、身份证和银行卡号 [ ] 不在高频循环中打印大对象 [ ] 请求体和响应体日志有长度限制 [ ] 异常日志不会重复打印多次 [ ] 单个日志文件有大小上限 [ ] 日志同时按时间和大小滚动 [ ] 历史日志有保留期限 [ ] 所有归档日志有总量上限 [ ] GC、访问和应用日志分别管理 [ ] 磁盘容量和 inode 都有告警 [ ] 已验证日志滚动与自动清理真正生效总结
这次事故表面上是磁盘写满,真正的故障链路是:
临时排查打开 DEBUG -> 问题解决后忘记恢复 -> 每个请求打印完整 JSON -> 日志每天增长约 100GB -> 没有单文件和总量上限 -> 磁盘达到 100% -> 应用和同机服务陆续异常一行日志不会立刻让系统崩溃。
但当它处在高频路径、打印大对象,并且没有任何容量边界时,一晚上足以写满整块磁盘。
记住三个原则:
日志只记录定位问题需要的信息;日志文件必须有滚动和总量上限;磁盘必须在写满之前告警。
如果你遇到服务器磁盘写满、Java 服务异常、日志滚动失效,或者需要协助安装部署 Spring Boot、MySQL、Redis、Nginx,可以关注我, 联系我。
简单问题、基础安装和初步排查,我可以免费帮忙看一下。发送配置和日志前请先脱敏,不要提供密码、Token、私钥与用户隐私数据。