日志系统设计:日志级别、日志格式规范、关键信息埋点、日志切割与归档
日志这东西,说白了就是系统的「黑匣子」。
我刚做数据回放那会儿,遇到线上问题第一反应就是翻日志。结果呢?要么日志级别乱设,要么关键信息没打出来,要么日志文件大到打不开。嗯,那叫一个痛苦。
后来我花了大力气把日志系统重新设计了一遍。今天就把这套经验分享给你。
一、日志级别:别什么都打INFO
日志级别是个老生常谈的话题,但真正用好的人不多。我个人习惯用这五个级别:
| 级别 | 含义 | 使用场景 |
|---|---|---|
| TRACE | 跟踪 | 调试时用的细粒度信息,生产环境一般不开启 |
| DEBUG | 调试 | 开发或排查问题时开启,记录变量值、执行路径 |
| INFO | 信息 | 系统正常运行的关键节点,比如启动、停止、任务完成 |
| WARN | 警告 | 潜在问题,不影响当前运行但需要关注 |
| ERROR | 错误 | 功能异常、数据不一致、系统故障 |
注意:别把INFO当DEBUG用。我在项目中遇到过有人把每个循环都打INFO,结果一天产生几十GB日志,磁盘直接爆了。
怎么选级别?记住一条原则:生产环境默认开INFO及以上。排查问题时再临时开DEBUG,问题解决后立刻关掉。
二、日志格式规范:统一才能解析
日志格式不统一,你连grep都懒得写。我建议所有日志都遵循这个模板:
[时间戳] [日志级别] [线程名] [类名:行号] - [请求ID] - 消息内容
举个例子:
[2025-03-15 14:30:22.123] [INFO] [main] [DataReplayService:56] - [req-abc123] - 开始回放任务,数据量:10000条
为什么要加请求ID?你想想看,一个服务同时处理多个请求,没有请求ID你根本分不清哪条日志属于哪个请求。我曾经排查一个并发问题,就是因为没打请求ID,花了整整两天才定位到问题。
小技巧:请求ID可以在入口处生成,用UUID或者雪花算法,然后通过MDC(Mapped Diagnostic Context)传递到整个调用链。
三、关键信息埋点:打什么、怎么打
埋点不是越多越好,而是关键节点必打。我总结了一套「四必打」原则:
- 入口必打:请求进来时,记录请求参数、来源、时间
- 出口必打:请求返回时,记录响应结果、耗时、状态码
- 异常必打:捕获到异常时,记录堆栈、上下文数据、当前状态
- 状态变更必打:任务状态从「待处理」变成「处理中」时,记录变更原因
举个例子,数据回放任务的埋点:
// 入口埋点
log.info("回放任务开始,taskId={}, 数据量={}, 开始时间={}", taskId, dataCount, startTime);
// 处理中埋点
log.info("回放进度,taskId={}, 已处理={}/{}", taskId, processed, total);
// 异常埋点
log.error("回放任务异常,taskId={}, 当前偏移量={}, 错误原因={}", taskId, offset, e.getMessage(), e);
// 出口埋点
log.info("回放任务结束,taskId={}, 总耗时={}ms, 成功={}, 失败={}", taskId, cost, success, fail);
核心思想:埋点要能支撑「问题复现」。如果一条日志能让你还原出当时的完整场景,那这个埋点就是成功的。
四、日志切割与归档:别让日志撑爆磁盘
日志不切割,后果很严重。我见过一个线上事故,就是因为日志文件太大,导致磁盘IO打满,服务直接挂掉。
常用的切割策略有两种:
- 按时间切割:每天一个文件,比如 app-2025-03-15.log
- 按大小切割:每个文件不超过100MB,超过就新建
我个人推荐按时间+大小双重切割。比如每天一个文件,但如果当天日志超过500MB,就自动拆分成多个文件。
归档策略也很重要:
| 策略 | 说明 | 建议 |
|---|---|---|
| 保留天数 | 只保留最近N天的日志 | 生产环境保留7-30天 |
| 压缩归档 | 将旧日志压缩后存储 | 使用gzip,压缩率可达90% |
| 冷热分离 | 热日志放本地,冷日志放对象存储 | 超过30天的日志自动迁移到OSS |
用Logback配置一个简单的切割:
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
避坑指南:我曾经因为没配maxHistory,日志文件累积了半年,磁盘空间从200GB变成2GB。从那以后,我每次上线都会检查日志保留策略。
五、知识体系总览
下面这张图,把日志系统设计的核心逻辑串起来了:
日志系统设计,说白了就是一套「写什么、怎么写、写完后怎么管」的规范。把这四点做好,你的系统就有了可靠的「黑匣子」。遇到问题,翻日志就能快速定位,不用再靠猜了。
最后说一句:日志系统不是一次性的工作。每次上线新功能,都要同步更新埋点。我习惯把日志埋点写进代码评审的checklist里,这样就不会漏了。
可可精品资料库