日志系统设计:日志级别、日志格式规范、关键信息埋点、日志切割与归档

日志这东西,说白了就是系统的「黑匣子」。

我刚做数据回放那会儿,遇到线上问题第一反应就是翻日志。结果呢?要么日志级别乱设,要么关键信息没打出来,要么日志文件大到打不开。嗯,那叫一个痛苦。

后来我花了大力气把日志系统重新设计了一遍。今天就把这套经验分享给你。

一、日志级别:别什么都打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。从那以后,我每次上线都会检查日志保留策略。

五、知识体系总览

下面这张图,把日志系统设计的核心逻辑串起来了:

日志系统设计核心逻辑 日志级别 TRACE/DEBUG/INFO/WARN/ERROR 日志格式规范 时间/级别/线程/类名/请求ID 关键信息埋点 入口/出口/异常/状态变更 切割 归档 设计要点: 1. 日志级别:生产环境默认INFO,排查问题临时开DEBUG 2. 日志格式:统一模板,方便grep和日志分析工具解析 3. 关键埋点:四必打原则,确保问题可复现 4. 切割归档:按时间+大小双重切割,保留30天,超期压缩 ⚠ 常见坑:日志级别设错、格式不统一、埋点遗漏、忘记归档

日志系统设计,说白了就是一套「写什么、怎么写、写完后怎么管」的规范。把这四点做好,你的系统就有了可靠的「黑匣子」。遇到问题,翻日志就能快速定位,不用再靠猜了。

最后说一句:日志系统不是一次性的工作。每次上线新功能,都要同步更新埋点。我习惯把日志埋点写进代码评审的checklist里,这样就不会漏了。

可可精品资料库