日志的账,早晚要算
日志的账,早晚要算
线上服务跑得好好的,磁盘突然就满了。这种报警,干过线上的人多半都撞见过,凌晨两三点把人叫起来,起来一看,日志文件好几个 G,大半是几个月前的旧数据,全写在一个文件里。先删,再加轮转,然后才想起来问一句,日志到底该怎么写。
问晚了。轮转只能治文件膨胀,治不了日志本身没法看。好多项目里日志是各写各的,有人打一行字符串,有人把整个对象序列化扔进去,密码和手机号也往里面打。出故障那天,一群人蹲在日志前面 grep,谁也拼不出完整的现场。
先把格式统一了。多数服务建议用 JSON,一行一条,带上时间、级别、业务 ID 和服务的名字。字段少而固定,写起来不费劲,后面接 Elasticsearch 或者 Loki 检索都顺。下面是 Python 里常见的一种写法,标准库 logging 加一个自定义 Formatter。
1 | |
格式定了,接着管级别。我的标准很简单,Error 是需要人去看的异常,Warning 是系统自己能恢复的状态,Info 记录关键业务节点,Debug 只留在本地。最容易坏的是 Warning,大家把它当另一种颜色的 Info 用,半年下来全是噪音,真出事反而淹在里面。
再就是别打敏感信息。明文密码、完整手机号、支付密钥,一行都不能进日志。这条最容易被人忽视,出事也最重。真要追链路,用脱敏过的短 ID。
文件切割是另一件事。日志不分文件,单文件会一直涨,涨到编辑器都打不开。可以按天切,也可以按大小切。常见做法是按天切,同时设一个单文件上限兜底,比如 200MB 就滚动,保留最近七天。
Linux 上最省事的办法是 logrotate,不用改业务代码,在 /etc/logrotate.d/ 下放一个配置就行。一个最简配置长这样。
1 | |
daily 是每天轮一次,rotate 14 是保留最近十四份,多了自动删。compress 用 gzip 压旧文件,delaycompress 让昨天的文件先不压,排查脚本还能读。missingok 是文件不在也别报错,notifempty 是空文件不轮转。
copytruncate 值得多说一句。正常轮转会先把文件改名,再让进程重开一个新文件,这要求程序能响应信号。程序接不了信号的时候才用 copytruncate,它先复制一份,再把原文件清空,进程不用重启。代价是复制和清空之间那一瞬写的日志会丢,量很小,但要知道有这个取舍。能响应信号,就别用 copytruncate。
配完可以手动验一次。
1 | |
常见的坑有三个。一个是 create 的参数没配对,文件重建出来权限和属主不对,进程写不进去。一个是搞不清 logrotate 什么时候跑,它挂在系统的 cron 上,多数发行版每天凌晨跑一次,具体几点看机器。发行版之间的差别也常坑人,CentOS 和 Ubuntu 的调度方式、默认参数不一样,照抄配置前先看看自己的机器。
轮转只是中间一环。日志切好以后还是躺在本机,出了问题照样要一台台翻。生产环境一般再上一套采集,Filebeat 或者 Promtail 把日志增量推到 ES 或者 Loki,配上 Grafana 做看板。上采集器的时候注意压缩格式,有些采集器读不了 .gz 里的增量。
容器里又是一套习惯。日志写到 stdout,由容器运行时统一收集,Pod 重建也不会丢。平台换来换去,输出规范和分级思想一直用得上。
写到这里,最想说的其实是开头那句。报警来了别先删,先问自己这套日志要是再写半年,还能不能看。规范定在出事之前,比半夜爬起来删文件便宜得多。