Logback 学习笔记
一、Java 日志体系总览
1.1 三层结构
你的代码(import org.slf4j.Logger)
↓
SLF4J(门面/接口层)------ 只定义 API,不干活
↓
Logback / Log4j2 / JUL(实际干活的实现层)
1.2 为什么需要门面
Java 生态里有多个日志框架(Log4j、JUL、Commons-Logging、Log4j2、Logback),第三方库各用各的。SLF4J 统一了接口,让所有日志最终汇聚到一个实现中,用一份配置管理。
1.3 门面模式(Facade Pattern)
GoF 23 种设计模式之一(结构型)。核心思想:为复杂子系统提供简化的统一接口。
Java 生态中的门面案例:
- SLF4J:日志门面,底层可换 Logback/Log4j2
- JDBC:数据库门面,底层可换 MySQL/PostgreSQL/Oracle 驱动
- Servlet API:HTTP 门面,底层可换 Tomcat/Jetty/Undertow
- Spring Cache:缓存门面,底层可换 Caffeine/Redis/EhCache
1.4 SLF4J 绑定机制
SLF4J 启动时通过 Class.forName("org.slf4j.impl.StaticLoggerBinder") 找到 classpath 上的实现。logback-classic.jar 里提供了这个类,所以 SLF4J 绑定到 Logback。
1.5 桥接器
让使用其他日志框架的第三方库也走 SLF4J:
log4j-over-slf4j:Log4j API → SLF4Jjul-to-slf4j:JUL → SLF4Jjcl-over-slf4j:Commons-Logging → SLF4J
Spring Boot 的 spring-boot-starter-logging 自动引入了这些桥接器。
二、Logback 三大核心概念
2.1 Logger(记录器)
- 是日志的入口,代码里
log.info()调的就是它 - 有唯一名字:
LoggerFactory.getLogger(类名)或LoggerFactory.getLogger("自定义字符串") - 有层级关系:按
.分隔形成树,root是所有 Logger 的祖先 - 有日志级别:TRACE < DEBUG < INFO < WARN < ERROR,低于阈值的不输出
- 有 additivity 属性:默认 true,日志会向上传递给父 Logger
2.2 Appender(输出器)
决定日志写到哪里:
| Appender | 用途 |
|---|---|
| ConsoleAppender | 输出到控制台 |
| RollingFileAppender | 输出到文件,按大小/时间滚动切割 |
| AsyncAppender | 包装器,把别的 appender 变成异步 |
| SMTPAppender | 发邮件告警 |
| SocketAppender | 发到远程服务器 |
一个 Logger 可以挂多个 Appender。
2.3 Encoder + Pattern(格式化器)
用 pattern 字符串定义日志输出格式。
三、LoggerFactory.getLogger 两种用法
3.1 传类名(99% 的场景)
java
LoggerFactory.getLogger(ApiCacheAdvice.class)
// 等价于
LoggerFactory.getLogger("com.xiaomi.mina.client.v1.aop.ApiCacheAdvice")
好处:
- 日志里能看出哪个类打的(pattern 里
%logger会显示类名) - 可以按包名批量控制级别(
<logger name="com.xiaomi" level="DEBUG">)
3.2 传自定义字符串(特殊场景)
java
LoggerFactory.getLogger("ACCESS_LOGGER")
用于需要独立输出到单独文件的场景(访问日志、慢 SQL 日志、审计日志等)。配合 logback.xml 里的 <logger name="ACCESS_LOGGER"> 使用。
3.3 @Slf4j 等价于什么
java
@Slf4j
public class MyClass { ... }
// Lombok 编译时自动生成:
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
四、mina-client logback.xml 逐段解读
4.1 变量定义
xml
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-...}"/>
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-...}"/>
<property scope="context" name="APP_NAME" value="mina-client-api"/>
<property name="LOG_HOME" value="${log.logdir:-logs}"/>
<property scope="context" name="LOG_ROOT_LEVEL" value="INFO"/>
${变量名:-默认值} 语法:先找变量,找不到就用默认值。
变量查找优先级:logback <property> → Java 系统属性 → 环境变量 → 默认值
scope="context" 表示在整个 logback 上下文中可见(包括 include 的文件)。不加 scope 默认是 local(只在当前文件)。
4.2 LOG_HOME 的实际值
- 生产环境:Dockerfile/start.sh 里
-Dlog.logdir=/home/work/logs→ LOG_HOME = /home/work/logs - 本地开发:没有 -D 参数 → 走默认值
logs(相对路径,项目根目录下的 logs/)
4.3 include
xml
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
defaults.xml:注册%clr颜色转换器 + 定义默认变量 + 屏蔽第三方库噪音日志console-appender.xml:提供一个现成的 CONSOLE appender(被项目自己的覆盖了)
4.4 access appender --- 访问日志
xml
<appender name="access" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/access.${APP_NAME}.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/access.${APP_NAME}.%d{yyyyMMdd}.%i.log</FileNamePattern>
<MaxHistory>1</MaxHistory> <!-- 只保留1天(已写入数据工场) -->
<maxFileSize>100MB</maxFileSize> <!-- 单文件超100MB就滚动 -->
</rollingPolicy>
</appender>
%d{yyyyMMdd}= 日期,%i= 当天序号- 归档示例:
access.mina-client-api.20260604.0.log
4.5 asyncAccess --- 异步包装
xml
<appender name="asyncAccess" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold> <!-- 队列满不丢日志(会阻塞) -->
<queueSize>1024</queueSize> <!-- 内存队列容量 -->
<includeCallerData>true</includeCallerData> <!-- 保留调用栈信息 -->
<appender-ref ref="access"/> <!-- 包装 access appender -->
</appender>
AsyncAppender 不是独立输出器,是包装器。业务线程只把消息塞进队列就返回,后台线程负责写盘。没有日志路径是因为它自己不写文件,转交给内部引用的 access appender 写。
discardingThreshold=0 的代价:磁盘 IO 太慢时队列满了会阻塞业务线程。默认值 20% 会丢弃低级别日志保护业务。这里配 0 说明访问日志一条不能丢。
4.6 FILE_ALL --- 全量日志
xml
<File>${LOG_HOME}/all.${APP_NAME}.log</File>
<MaxHistory>7</MaxHistory> <!-- 保留7天 -->
<maxFileSize>10MB</maxFileSize>
4.7 FILE_ERROR --- 错误日志
xml
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
LevelFilter 只收 ERROR,其他级别全拒绝。
4.8 ACCESS_LOGGER --- 自定义命名 Logger
xml
<logger name="ACCESS_LOGGER" additivity="false" level="INFO">
<appender-ref ref="asyncAccess"/>
</logger>
- additivity="false":禁止向上传递,日志不会进 root 的 appender(不会重复输出到 all/console)
- 只绑定 asyncAccess 一个 appender → 日志只进 access.log
4.9 root logger --- 兜底
xml
<root level="${LOG_ROOT_LEVEL}">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="FILE_ERROR"/>
</root>
所有没被专门配置 <logger> 的 Logger 都走 root。@Slf4j 生成的业务日志同时输出到控制台 + all 文件 + err 文件。
五、Pattern 详解
5.1 FILE_LOG_PATTERN
%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%X{trace_id}|%15.15t|%-40.40logger{39}|%m%n%xException
| 片段 | 含义 | 示例输出 |
|---|---|---|
%d{yyyy-MM-dd HH:mm:ss.SSS} |
时间(毫秒精度) | 2026-06-05 11:30:45.123 |
%-5level |
日志级别,左对齐宽度5 | INFO |
%X{trace_id} |
MDC 变量(链路追踪ID) | abc123def456 |
%15.15t |
线程名,固定宽度15 | http-nio-8080-1 |
%-40.40logger{39} |
Logger名,缩写到39字符,左对齐宽度40 | c.x.m.client.v1.aop.ApiCacheAdvice |
%m |
日志消息正文 | 缓存命中 key=xxx |
%n |
换行 | |
%xException |
异常堆栈 | java.lang.NullPointerException... |
最终输出示例:
2026-06-05 11:30:45.123|INFO |abc123def456|http-nio-8080-1|c.x.m.client.v1.aop.ApiCacheAdvice |缓存命中 key=xxx
5.2 CONSOLE_LOG_PATTERN
%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(${level:-%5p}) --- [%15.15t] %clr(%-40.40logger{39}){cyan} : %m%n%xEx
%clr(内容):自动按级别着色(INFO=绿, WARN=黄, ERROR=红),只在控制台有效%clr(内容){cyan}:固定青色- 用空格 +
---+[]分隔(人眼友好)
5.3 格式化占位符速查
| 符号 | 含义 |
|---|---|
%d |
日期时间 |
%p 或 %level |
日志级别 |
%t 或 %thread |
线程名 |
%logger{N} |
Logger 名(N=最大字符数,超长会缩写包名) |
%m 或 %msg |
消息体 |
%n |
换行符 |
%X{key} |
MDC 变量 |
%xEx / %xException |
异常堆栈(含包信息) |
%-N |
左对齐,最小宽度N |
%N.N |
最小宽度.最大宽度(超长截断) |
六、同步 vs 异步
| Logger | 绑定的 appender | 同步/异步 |
|---|---|---|
| @Slf4j 生成的(类名 Logger) | CONSOLE + FILE_ALL + FILE_ERROR | 同步 |
| ACCESS_LOGGER(手动创建的) | asyncAccess | 异步 |
业务代码里的 log.info、log.error 是同步写磁盘的。只有访问日志走异步。
七、Spring Boot 与 logback.xml 的交互
7.1 两种配日志的方式
| 方式 | 适合 |
|---|---|
application.yml 的 logging.* |
简单项目,不想写 logback.xml |
| 自定义 logback.xml | 复杂项目,需要精细控制 |
7.2 有 logback.xml 时 yml 配置的生效情况
| yml 配置 | 是否生效 | 原因 |
|---|---|---|
logging.level.root |
生效 | Spring Boot 强制调 API 设级别 |
logging.level.com.xxx |
生效 | 同上 |
logging.file.name |
看情况 | 注入为 LOG_FILE 变量,logback.xml 里引用了才有用 |
logging.pattern.console |
看情况 | 注入为 CONSOLE_LOG_PATTERN 变量,logback.xml 里引用了才有用 |
7.3 Spring Boot 注入变量的机制
yml: logging.pattern.console = "%d %p %m%n"
→ Spring Boot: System.setProperty("CONSOLE_LOG_PATTERN", "%d %p %m%n")
→ logback 解析 ${CONSOLE_LOG_PATTERN:-默认值}
→ 找到系统属性,用它的值
八、常用配置场景
- 按环境切换 :
<springProfile name="dev">/<springProfile name="production"> - 第三方库降噪 :
<logger name="org.redisson" level="WARN"/> - 独立日志文件:命名 Logger + additivity=false + 单独 appender
- 全异步:所有 appender 都用 AsyncAppender 包一层
- 慢 SQL 日志:和 ACCESS_LOGGER 同一套路
- ERROR 告警:SMTPAppender 或 Prometheus + AlertManager
九、MDC(Mapped Diagnostic Context)
线程级的键值对变量,用于链路追踪。
java
MDC.put("trace_id", "abc123"); // 请求进入时设置
log.info("处理中"); // 日志自动带上 %X{trace_id} = abc123
MDC.remove("trace_id"); // 请求结束时清除(避免线程池复用污染)
Pattern 里用 %X{trace_id} 引用。同一个请求在多个类里打的日志都带同一个 trace_id,grep 一下就能串起整条链路。