4. 日志切割与归档:别让日志撑爆你的磁盘
日志这东西,不切不行。我见过太多线上事故,最后查下来根因居然是「磁盘写满了,服务挂了」。你想想看,一个不设防的日志文件,在高并发下一天能涨到几十个 GB。所以今天咱们聊聊怎么管好这些日志——切割、归档、保留、收集,一套组合拳打下来,磁盘再也不会报警。
4.1 为什么要做日志切割?
说白了,就是一个文件不能无限写下去。原因有三:
- 磁盘空间有限——单个日志文件撑爆分区,服务直接宕机。
- 查询效率低下——一个 10GB 的文本文件,你用
grep搜个关键词,等得花儿都谢了。 - 归档管理困难——没有切割,你就没法按天或按小时做历史回溯。
我在项目中遇到过一台 Nginx 服务器,access.log 三个月没切,最后涨到 47GB。运维同学想用 tail 看实时日志,结果 tail 直接卡死。嗯,从那以后我养成了习惯:日志切割必须写在部署脚本里,谁忘了谁请下午茶。
4.2 按时间切割:最常用的策略
按时间切,是最直观的做法。常见的有按小时、按天、按周。我个人习惯按天切,因为粒度适中,既不会产生太多小文件,也不会让单文件太大。
Linux 下最常用的工具是 logrotate。它的配置非常灵活,我贴一个我常用的模板:
/var/log/myapp/*.log {
daily # 按天切割
rotate 30 # 保留最近 30 份
compress # 压缩旧日志(gzip)
delaycompress # 延迟一天压缩,方便当天查
missingok # 日志文件不存在也不报错
notifempty # 空文件不切割
dateext # 用日期作为后缀
dateformat -%Y%m%d # 日期格式:-20250321
sharedscripts # 多个日志文件共享一个脚本
postrotate
/usr/bin/killall -HUP myapp 2>/dev/null || true
endscript
}
delaycompress 这个参数很实用。切割当天生成的旧日志还是文本格式,方便你临时查问题。第二天才会被压缩成 .gz,既省空间又不影响排查。
为什么用 killall -HUP?因为很多应用(比如 syslog-ng、Nginx)收到 HUP 信号后会重新打开日志文件句柄。如果不做这一步,切割后日志还会继续往旧文件里写,等于白切了。
4.3 按大小切割:应对突发流量
按时间切有个问题:如果某天流量暴增,日志量可能是平时的 10 倍。按天切的话,单文件还是会很大。这时候就需要按大小切割作为补充。
还是 logrotate,配置稍微改一下:
/var/log/myapp/*.log {
size 100M # 日志超过 100MB 就切
rotate 50 # 保留 50 份
compress
missingok
notifempty
dateext
dateformat -%Y%m%d-%H%M%S # 精确到秒,避免重名
postrotate
/usr/bin/killall -HUP myapp 2>/dev/null || true
endscript
}
这里有个坑:size 和 daily 可以同时配置,logrotate 会按「谁先触发就切谁」的原则执行。我曾经踩过这个坑——同时配了 size 100M 和 daily,结果一天切了 80 多次,生成了 80 多个小文件,归档系统直接报警。后来我改成只按大小切,或者只按时间切,二选一,别贪心。
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按时间切割 | 流量平稳、可预测 | 文件命名规律,便于回溯 | 突发流量下文件可能过大 |
| 按大小切割 | 流量波动大、不可预测 | 单文件大小可控 | 文件数量多,命名杂乱 |
| 时间+大小混合 | 需要双重保障 | 兼顾规律性和可控性 | 配置复杂,可能产生过多文件 |
4.4 日志保留策略:该删就删
日志不能无限保留。我见过有些团队把日志保留 365 天,结果磁盘成本比服务器还高。合理的保留策略应该根据业务需求来定:
- 调试日志(DEBUG/TRACE):保留 3-7 天,主要用于排查近期问题。
- 业务日志(INFO/WARN):保留 30 天,满足大部分审计和回溯需求。
- 错误日志(ERROR/FATAL):保留 90 天,用于长期趋势分析。
- 安全审计日志:保留 180 天以上,合规要求。
在 logrotate 里,rotate 30 就是保留最近 30 份。配合 maxage 30 可以按天数删除:
/var/log/myapp/*.log {
daily
rotate 30
maxage 30 # 超过 30 天的日志直接删除
compress
...
}
rotate 和 maxage 是「或」的关系,谁先满足就触发删除。比如你配了 rotate 30 和 maxage 60,当文件数量超过 30 份时就会删除最旧的,即使它还没到 60 天。
4.5 集中式日志收集:告别 SSH 到每台机器
服务器一多,每台机器上去 tail -f 就不现实了。你需要一个集中式日志收集系统。常见的方案有 ELK(Elasticsearch + Logstash + Kibana)、Loki + Promtail、以及轻量级的 Filebeat + Kafka + Logstash。
我个人比较推荐 Filebeat + Kafka + Logstash 这个链路,原因有三:
- Filebeat 足够轻量——资源占用极低,不会影响业务进程。
- Kafka 做缓冲——即使下游 Logstash 挂了,日志也不会丢。
- Logstash 做过滤和转换——可以解析 JSON、提取字段、过滤掉无用的 DEBUG 日志。
一个典型的 Filebeat 配置长这样:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/myapp/*.log
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
multiline.negate: true
multiline.match: after
output.kafka:
hosts: ["kafka1:9092", "kafka2:9092"]
topic: "myapp-logs"
partition.round_robin:
reachable_only: true
这里有个细节:multiline 配置。Java 应用的异常栈是跨多行的,如果不做多行合并,一条异常会被拆成几十条日志,查问题的时候根本没法看。我当初第一次配 Filebeat 时就忘了这个,结果 Kibana 里看到一堆碎片化的堆栈信息,排查了半小时才发现是配置问题。
4.6 整体架构图:从切割到收集
下面这张图展示了从日志产生到集中存储的完整链路。我把它画成了 SVG,方便你理解各组件之间的关系。
4.7 避坑指南:我踩过的几个坑
最后分享几个实战中容易踩的坑,希望能帮你省点时间:
- 日志文件句柄未释放——切割后应用还在往旧文件写。解决方案:确保
postrotate里发送了 HUP 信号,或者应用本身支持copytruncate模式。 - 压缩导致 CPU 飙高——如果日志量巨大,
compress会在切割瞬间消耗大量 CPU。我建议用delaycompress错开高峰期,或者用nocompress配合外部定时压缩任务。 - Kafka 主题分区不足——Filebeat 往 Kafka 写日志时,如果分区数太少,会导致部分分区负载过高。我一般把分区数设为消费者组数量的 3-5 倍。
- 多行日志合并失败——Java 异常栈、SQL 慢查询日志都是多行的。Filebeat 的
multiline正则一定要写对,否则日志在 Kibana 里就是一团乱麻。
核心总结:
- 日志切割用
logrotate,按时间或按大小,二选一别混用。 - 保留策略根据日志级别定,DEBUG 3 天、ERROR 90 天,别一刀切。
- 集中收集推荐 Filebeat → Kafka → Logstash → Elasticsearch,链路清晰且可靠。
- 别忘了多行合并和文件句柄释放,这两个坑我至少各踩过一次。