3. 日志配置实战:Spring Boot 中 Logback 的 XML 配置详解
说实话,很多团队把日志配置当成「一次性工作」——项目初始化时配一次,后面再也没人动过。直到线上出问题,才发现日志要么把磁盘撑爆了,要么关键信息根本没打出来。
我个人习惯是,每个项目的日志配置都要亲手过一遍。因为默认配置只能保证「能跑」,但离「好用」还差得远。今天我们就来拆解 Logback 的 XML 配置,看看每一行到底在干什么。
3.1 Logback 配置文件的加载顺序
Spring Boot 加载 Logback 配置时,有个优先级顺序。我记得刚带团队时,有个同事在 application.yml 里配了日志路径,又在 logback-spring.xml 里配了另一套,结果两边都没生效。为什么会这样?
加载顺序是这样的:
- classpath 下的
logback-spring.xml—— 这是 Spring Boot 推荐的方式,支持<springProfile>标签 - classpath 下的
logback.xml—— 标准 Logback 配置,但不支持 Spring 扩展 logging.config属性指定的路径 —— 可以完全自定义- Spring Boot 的默认配置 —— 如果上面都没有,就用内置的
base.xml
logback-spring.xml。这样既能用 Spring 的 Profile 功能,又能保持配置的集中管理。别把配置散落在 yml 和 xml 两个文件里,后期维护起来很痛苦。
3.2 配置文件的整体结构
一个完整的 Logback XML 配置,说白了就是三大部分:Appender(输出到哪里)、Logger(谁在记录)、Layout(记录成什么样)。
下面这张图可以帮你快速建立整体认知:
3.3 Appender 配置详解
Appender 决定了日志的去向。我个人最常用的就三种:控制台、滚动文件、异步。下面逐个说。
3.3.1 ConsoleAppender
开发环境必备。配置很简单,但有个坑——默认输出到 System.err,这在某些容器环境下会导致日志顺序错乱。
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 关键:指定输出到 System.out -->
<target>System.out</target>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<target>System.out</target>。
3.3.2 RollingFileAppender
生产环境的核心 Appender。它解决了单个日志文件无限增长的问题。配置时要注意三个参数:
| 参数 | 作用 | 我的推荐值 |
|---|---|---|
maxFileSize |
单个日志文件的最大大小 | 200MB(太小频繁滚动影响性能,太大不利于分析) |
maxHistory |
保留的历史文件个数 | 30 天(根据磁盘容量和法规要求调整) |
totalSizeCap |
所有日志文件的总大小上限 | 10GB(防止日志把磁盘占满) |
<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">
<fileNamePattern>/var/log/myapp/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>200MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
%i 是滚动索引。同一天内如果文件超过 maxFileSize,会自动生成 app.2024-01-01.0.log、app.2024-01-01.1.log 这样的文件。这个设计很巧妙,既按天归档,又按大小拆分。
3.3.3 异步 Appender(AsyncAppender)
你想想看,如果每次写日志都要等磁盘 I/O,你的业务线程就会被阻塞。异步 Appender 就是为了解决这个问题的。
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 引用实际的 Appender -->
<appender-ref ref="FILE"/>
<!-- 队列大小,默认 256 -->
<queueSize>1024</queueSize>
<!-- 队列满时丢弃日志,不阻塞业务线程 -->
<discardingThreshold>0</discardingThreshold>
<!-- 是否在应用停止时等待队列处理完 -->
<neverBlock>false</neverBlock>
</appender>
queueSize 设成了 10000,但业务流量峰值时每秒产生 5 万条日志。结果队列满了之后,日志全部丢弃,线上排查问题时发现关键日志没了。所以队列大小要根据你的业务量来算,一般 1024 或 2048 就够用。
3.4 Logger 与 Root 配置
Logger 配置决定了「谁」的日志被记录到「哪里」。这里有个继承关系,很多人搞混。
- Root Logger:所有 Logger 的根,默认级别是
ERROR - 自定义 Logger:可以覆盖某个包或类的级别和 Appender
- 继承机制:子 Logger 如果没有显式配置 Appender,会继承父 Logger 的 Appender
<!-- 根 Logger,所有未配置的包都走这里 -->
<root level="INFO">
<appender-ref ref="ASYNC"/>
<appender-ref ref="CONSOLE"/>
</root>
<!-- 业务包,单独设置 DEBUG 级别 -->
<logger name="com.mycompany.business" level="DEBUG" additivity="false">
<appender-ref ref="ASYNC"/>
</logger>
<!-- 第三方库,只记录 WARN 以上,减少噪音 -->
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate" level="WARN"/>
additivity="false" 表示不继承父 Logger 的 Appender。如果你设了 additivity="false" 但又没给子 Logger 配 Appender,那这个包的所有日志都会丢失。嗯,这个坑我见过不止一次。
3.5 Pattern 布局详解
日志格式看起来是小事,但真到排查问题时,格式好不好直接决定效率。我个人习惯的格式包含以下要素:
| 占位符 | 含义 | 示例 |
|---|---|---|
%d |
日期时间 | 2024-01-01 14:30:00.123 |
%thread |
线程名 | http-nio-8080-exec-3 |
%-5level |
日志级别(左对齐,5 字符宽) | INFO 、ERROR |
%logger{36} |
Logger 名称(缩写包名) | c.m.b.service.UserService |
%msg |
日志消息 | 用户登录成功 |
%n |
换行 |
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
%cyan()、%green() 等颜色标记,让不同级别的日志一目了然。但生产环境不要加颜色,否则日志文件里会多出 ANSI 转义字符,影响日志采集工具解析。
3.6 Spring Profile 支持
Spring Boot 的 logback-spring.xml 支持 <springProfile> 标签,可以针对不同环境加载不同配置。这个功能非常实用。
<!-- 开发环境:控制台输出 DEBUG 级别 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 生产环境:异步文件输出 INFO 级别 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC"/>
</root>
</springProfile>
logback-spring.xml 支持,logback.xml 不行。如果你用 logback.xml,又想区分环境,就得用条件判断(Janino 依赖),那配置就复杂多了。
3.7 一个完整的生产配置示例
最后,给你一个我目前在用的生产配置模板。你可以直接拿去改改就用。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义属性,方便统一管理 -->
<property name="LOG_PATH" value="/var/log/myapp"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台 Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 滚动文件 Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>200MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 异步 Appender -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
<!-- 开发环境 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC"/>
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC"/>
</root>
<logger name="com.mycompany.business" level="DEBUG" additivity="false">
<appender-ref ref="ASYNC"/>
</logger>
</springProfile>
</configuration>
这个配置我用了两年多,在日均千万级日志量的系统上验证过。你拿过去之后,只需要改 LOG_PATH 和包名就行。