日志系统搭建:日志级别配置、日志格式规范、日志轮转策略

好,咱们进入第二章。日志系统搭建,听起来像个基础活,对吧?但说实话,我见过太多项目栽在这个“基础活”上。要么日志刷爆磁盘,要么关键时刻啥也查不到。今天我就把这几年的经验掰开揉碎,跟你聊聊。

一、日志级别配置:别让“废话”淹没了真相

日志级别,说白了就是给日志分个三六九等。我个人习惯用这五个级别:TRACE、DEBUG、INFO、WARN、ERROR。你想想看,线上环境如果开了DEBUG,那日志量能把你吓死。

核心原则:生产环境用INFO及以上,开发环境可以开DEBUG。千万别在生产开TRACE,除非你想让磁盘报警。

我在项目中遇到过一件事:有个同事把日志级别设成了DEBUG,结果一天下来日志文件涨到50GB。嗯,磁盘直接满了,服务挂了。从那以后,我定了个规矩——日志级别必须通过配置中心动态调整

举个例子,Java项目里用Logback,配置是这样的:

<!-- logback.xml -->
<configuration>
    <!-- 生产环境级别 -->
    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
    
    <!-- 某个模块需要DEBUG时,单独开 -->
    <logger name="com.example.payment" level="DEBUG"/>
</configuration>

为什么要单独开?因为支付模块出问题概率高,我习惯给它开个小灶。其他模块保持INFO,省得日志淹没了关键信息。

我的小技巧:在ERROR级别日志里,一定要带上异常堆栈。别只打个"出错了",那等于没打。我曾经排查一个内存泄漏,全靠ERROR日志里的堆栈信息定位到具体代码行。

二、日志格式规范:让机器和人一眼看懂

日志格式,很多人不重视。觉得能看就行。但我要说,格式不规范,排查两行泪

我建议的日志格式包含以下字段:

字段 说明 示例
时间戳 精确到毫秒,带时区 2024-01-15 14:30:22.123 +0800
线程名 方便追踪并发问题 [http-nio-8080-exec-3]
日志级别 5个字符宽度,对齐 [INFO ]
类名/方法名 精确到方法 com.example.UserService.getUser
请求ID 链路追踪关键 [traceId=abc123]
消息体 业务描述+关键参数 用户登录成功, userId=10086

看个实际配置:

<!-- 日志格式定义 -->
<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n
</pattern>

这里有个坑,我踩过。就是请求ID必须在线程间传递。异步任务里如果没处理好,traceId就丢了。我曾经排查一个跨线程的问题,日志里traceId全是空的,那叫一个抓狂。

避坑指南:我曾经因为日志格式里没加traceId,排查一个分布式事务问题花了整整两天。后来加上后,半小时就定位到了。所以,traceId必须加,而且要贯穿整个调用链

三、日志轮转策略:别让日志撑爆你的磁盘

日志轮转,说白了就是让日志文件“自动翻篇”。不轮转的话,一个文件写到天荒地老,你根本打不开。

我常用的策略是按大小+按时间混合轮转

  • 按大小:单个文件超过100MB就切
  • 按时间:每天生成一个新文件
  • 保留策略:保留最近7天,或者总大小不超过5GB

看个Logback的配置:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/var/log/myapp/app.log</file>
    
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- 文件名模式,%d是日期,%i是序号 -->
        <fileNamePattern>/var/log/myapp/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <!-- 单个文件最大100MB -->
        <maxFileSize>100MB</maxFileSize>
        <!-- 保留30天 -->
        <maxHistory>30</maxHistory>
        <!-- 总大小上限 -->
        <totalSizeCap>10GB</totalSizeCap>
    </rollingPolicy>
    
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
    </encoder>
</appender>

这里要注意几个点:

  • maxHistory和totalSizeCap要配合使用。光设天数,万一某天日志量暴增,照样撑爆。光设大小,可能把重要日期的日志删了。
  • 文件名模式别用错了。%d和%i的顺序有讲究,我习惯把日期放前面,序号放后面,这样按日期排序时一目了然。
  • 异步Appender。如果日志量很大,建议用异步写入,别让日志拖慢业务线程。

我的经验:日志轮转的压缩也很重要。我习惯把历史日志用gzip压缩,能省70%的磁盘空间。配置里加个.gz后缀就行:app.%d{yyyy-MM-dd}.%i.log.gz

四、实战中的那些坑

嗯,最后聊几个我踩过的坑,你遇到了能少走弯路。

  1. 日志文件权限问题:我曾经部署一个新服务,日志目录权限没配好,结果服务启动后日志写不进去,排查了半天才发现是权限问题。建议统一用chmod 755,属主设为应用用户。
  2. 日志目录磁盘空间监控:别等磁盘满了才想起来。我习惯在监控系统里加个告警,日志目录使用率超过80%就发通知。
  3. 日志清理的“坑”:有些框架的日志轮转是“假轮转”,只重命名文件不删除旧文件。我遇到过一台机器上积累了半年的日志,足足200GB。所以,一定要验证轮转策略是否真的生效
  4. 多实例日志冲突:微服务架构下,多个实例写同一个日志文件?千万别。每个实例要有独立的日志文件,最好带上主机名或实例ID。

最后提醒:日志系统搭建好了,一定要做压力测试。我曾经在压测时发现日志写入成了瓶颈,TPS直接掉了30%。后来改成异步Appender才解决。所以,别让日志成为性能杀手

好了,这一章就聊到这儿。日志系统看似简单,但细节决定成败。下一章咱们聊聊如何从日志里快速定位内存泄漏,那才是真正的硬核内容。