Logback 是 Java 生态系统中使用最广泛的日志框架之一,作为 Log4j 的继任者和 SLF4J 的原生实现,它以其卓越的性能、灵活的配置和丰富的功能成为现代 Java 应用的首选日志解决方案。

Logback 简介

什么是 Logback

Logback 由 Log4j 创始人 Ceki Gülcü 开发,设计上分为三个核心模块:

模块作用
logback-core基础框架,为其他两个模块提供支持
logback-classicSLF4J 的原生实现,包含核心日志功能
logback-access与 Servlet 容器集成,提供 HTTP 访问日志功能

为什么选 Logback

1
2
3
4
5
6
7
8
9
10
11
性能优势:
- 初始化速度比 Log4j 快约 10 倍
- 内存占用更少
- 异步 Appender 吞吐量更高
- 自动重载配置无需重启应用

功能优势:
- 原生支持 SLF4J
- 条件化配置(if/then/else)
- 自动压缩归档日志
- 更强大的过滤器

核心架构

三大核心组件

1
2
3
4
5
6
7
8
9
10
11
// Logger - 日志记录器,负责捕获日志信息
Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("用户 {} 登录成功", username);

// Appender - 日志输出目标,决定日志写到哪里
// ConsoleAppender -> 控制台
// FileAppender -> 文件
// RollingFileAppender -> 滚动文件

// Layout/Encoder - 日志格式化,定义输出样式
// PatternLayoutEncoder 是最常用的

日志级别层级

1
2
3
4
5
ERROR > WARN > INFO > DEBUG > TRACE > ALL
^ |
| |
+---------------------------------------+
(继承关系)

级别设置规则:只输出级别 >= 设定级别的日志

1
2
3
4
// 如果 root logger 设置为 INFO
logger.debug("这行不会输出"); // DEBUG < INFO,忽略
logger.info("这行会输出"); // INFO >= INFO,输出
logger.error("这行会输出"); // ERROR > INFO,输出

配置详解

XML 配置(最常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义变量 -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="LOG_DIR" value="logs"/>

<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_DIR}/application.log</file>
<append>true</append>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>

<!-- 滚动文件输出(生产环境推荐) -->
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/app.log</file>

<!-- 滚动策略:按时间和文件大小 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天生成一个新文件 -->
<fileNamePattern>${LOG_DIR}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 保留 30 天 -->
<maxHistory>30</maxHistory>
<!-- 总大小限制 3GB -->
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>

<!-- 触发策略:文件超过 100MB 滚动 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>

<!-- 异步 Appender(高性能场景) -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 队列大小,默认 256 -->
<queueSize>512</queueSize>
<!-- 丢弃策略:队列满时丢弃 INFO 及以下级别 -->
<discardingThreshold>0</discardingThreshold>
<!-- 不丢失日志事件 -->
<neverBlock>false</neverBlock>
<appender-ref ref="ROLLING_FILE"/>
</appender>

<!-- 包级别的日志级别配置 -->
<logger name="com.example.dao" level="DEBUG"/>
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>

<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC"/>
</root>
</configuration>

配置自动重载

1
2
3
4
<!-- 每 60 秒扫描配置变化 -->
<configuration scan="true" scanPeriod="60 seconds">
...
</configuration>

条件化配置(多环境支持)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<configuration>
<!-- 根据环境变量选择配置 -->
<if condition='property("env").contains("dev")'>
<then>
<property name="LOG_LEVEL" value="DEBUG"/>
</then>
<else>
<property name="LOG_LEVEL" value="INFO"/>
</else>
</if>

<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

日志格式详解

常用 Pattern 符号

符号含义示例输出
%d{pattern}日期时间%d{yyyy-MM-dd HH:mm:ss} → 2024-01-15 14:30:25
%level / %le / %p日志级别INFO, DEBUG, ERROR
%logger{len}Logger 名(可缩写)%logger{36} → c.e.m.MyService
%msg / %m日志消息实际日志内容
%thread / %t线程名main, pool-1-thread-2
%class调用类名(慢,不推荐生产用)com.example.MyClass
%method / %M调用方法名(慢)doSomething
%line / %L行号(慢)42
%file / %F文件名(慢)MyClass.java
%ex / %exception异常堆栈完整的 stack trace
%n换行符平台相关
%X{key}MDC 变量见下文 MDC 章节
%markerMarker 标记特殊标记,用于过滤

颜色输出(IDE/终端友好)

1
2
3
4
5
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>

颜色说明:

  • %highlight() - 根据级别自动着色(ERROR=红,WARN=黄,INFO=绿,DEBUG=蓝)
  • %cyan() / %magenta() / %yellow() 等 - 指定颜色

代码中使用

基础用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
// 推荐:用类名作为 logger 名
private static final Logger logger = LoggerFactory.getLogger(UserService.class);

public void login(String username, String password) {
// 简单日志
logger.info("用户登录请求");

// 带参数的日志(推荐,避免字符串拼接开销)
logger.info("用户 {} 尝试登录", username);

// 多参数
logger.debug("用户 {} 从 IP {} 登录,时间 {}",
username, clientIp, LocalDateTime.now());

// 判断级别(避免参数计算开销)
if (logger.isDebugEnabled()) {
// 复杂参数只在 DEBUG 级别计算
logger.debug("详细对象信息: {}", expensiveToString());
}
}
}

异常日志

1
2
3
4
5
6
7
8
9
10
11
12
try {
riskyOperation();
} catch (BusinessException e) {
// 正确:记录异常,最后一个参数放异常对象
logger.error("业务处理失败,订单号: {}", orderNo, e);
} catch (Exception e) {
// 错误示范:不要这样!会丢失堆栈
// logger.error("发生错误: " + e.getMessage());

// 正确
logger.error("系统异常", e);
}

使用 Markers 进行高级过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class AuditService {
// 创建标记
private static final Marker AUDIT_MARKER = MarkerFactory.getMarker("AUDIT");
private static final Marker SECURITY_MARKER = MarkerFactory.getMarker("SECURITY");

static {
SECURITY_MARKER.add(AUDIT_MARKER); // 标记继承
}

public void deleteUser(Long userId) {
// 带标记的日志
logger.info(SECURITY_MARKER, "删除用户 {}, 操作人 {}", userId, currentUser);
}
}

配合配置过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<appender name="AUDIT_FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/audit.log</file>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boo.JaninoEventEvaluator">
<expression>
marker != null && marker.contains("AUDIT")
</expression>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>

MDC(Mapped Diagnostic Context)

MDC 是 Logback 的杀手锏功能,用于在日志中添加上下文信息(如用户ID、请求ID、TraceID 等)。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.slf4j.MDC;

public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);

public void createOrder(CreateOrderRequest request) {
// 1. 放入上下文
MDC.put("userId", request.getUserId());
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("traceId", request.getTraceId());

try {
logger.info("开始创建订单");
// 业务逻辑...
orderService.create(request);
logger.info("订单创建成功,订单号: {}", orderNo);
} finally {
// 2. 必须清理!否则内存泄漏
MDC.clear();
}
}
}

配合 Pattern

1
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger - %msg%n</pattern>

输出示例:

14:30:25.123 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] INFO c.e.s.OrderService - 开始创建订单
14:30:25.145 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] DEBUG c.e.s.OrderService - 参数验证通过
14:30:25.200 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] INFO c.e.s.OrderService - 订单创建成功,订单号: ORDER_20240115_001

Spring Boot 集成(自动填充 MDC)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class MdcInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String traceId = request.getHeader("X-Trace-Id");
if (traceId == null) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put("traceId", traceId);
MDC.put("clientIp", getClientIp(request));
return true;
}

@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
MDC.clear();
}
}

高级特性

日志分离(不同业务模块写不同文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 订单模块日志 -->
<appender name="ORDER_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/order/order.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/order/order.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>

<!-- 支付模块日志 -->
<appender name="PAY_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/pay/pay.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/pay/pay.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory> <!-- 支付日志保留更久 -->
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>

<logger name="com.example.order" level="INFO" additivity="false">
<appender-ref ref="ORDER_LOG"/>
<appender-ref ref="CONSOLE"/>
</logger>

<logger name="com.example.pay" level="INFO" additivity="false">
<appender-ref ref="PAY_LOG"/>
<appender-ref ref="CONSOLE"/>
</logger>

注意 additivity="false" 防止日志重复输出到 root。

SMTP 告警(错误邮件通知)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<smtpHost>smtp.company.com</smtpHost>
<smtpPort>587</smtpPort>
<username>alert@company.com</username>
<password>password</password>
<to>ops@company.com</to>
<from>alert@company.com</from>
<subject>%logger{20} - %m</subject>

<!-- 只发送 ERROR 级别 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>

<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss} %level %logger - %msg%n%ex</pattern>
</layout>
</appender>

日志脱敏(自定义 Encoder)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MaskingPatternEncoder extends PatternLayoutEncoder {

private Pattern pattern = Pattern.compile("(phone|idCard|password)=([^&]*)");
private String replacement = "$1=***";

@Override
public byte[] encode(ILoggingEvent event) {
String formatted = layout.doLayout(event);
// 脱敏处理
String masked = pattern.matcher(formatted).replaceAll(replacement);
return masked.getBytes(StandardCharsets.UTF_8);
}
}

Spring Boot 集成

依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 包含 spring-boot-starter-logging,自动引入 logback -->

application.yml 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30

多环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# application-dev.yml
logging:
level:
root: DEBUG
com.example: TRACE

# application-prod.yml
logging:
level:
root: WARN
com.example: INFO
pattern:
console: "%d{HH:mm:ss} [%X{traceId}] %-5level - %msg%n"

性能优化建议

DO(应该做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 使用参数化日志,避免字符串拼接
logger.debug("User {} logged in from {}", user, ip);
// 而不是:logger.debug("User " + user + " logged in from " + ip);

// 2. 异步日志应对高并发
// 使用 <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">

// 3. 关闭不必要的行号、方法名输出(%L, %M 很耗性能)
// 生产环境用:%d %level %logger{36} - %msg%n

// 4. 批量写入(增大缓冲区)
// <immediateFlush>false</immediateFlush>

// 5. 使用 isXxxEnabled 避免昂贵参数计算
if (logger.isDebugEnabled()) {
logger.debug("Result: {}", heavyComputation());
}

DON’T(不要做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 不要创建多个 Logger 实例
// 错误:每次 new LoggerFactory.getLogger(...)
// 正确:static final Logger

// 2. 不要记录过多 DEBUG 信息在生产环境
// 使用条件配置或动态调整级别

// 3. 不要在循环中记录大量日志
for (Item item : items) {
logger.debug("Processing item: {}", item); // 如果 items 很多,危险!
}
// 改为:logger.debug("Processed {} items", items.size());

// 4. 不要记录敏感信息
logger.info("User password: {}", password); // 绝对不要!

常见问题排查

日志不输出

检查清单:

  1. logback.xml 是否在 src/main/resources 目录
  2. 文件名是否正确(区分大小写)
  3. 日志级别是否设置正确
  4. 包路径是否匹配 <logger name="...">

日志重复

原因:Logger 的 additivity 默认为 true,日志会向上传播给父 Logger。

1
2
3
4
<!-- 解决:设置 additivity="false" -->
<logger name="com.example.dao" level="DEBUG" additivity="false">
<appender-ref ref="DAO_LOG"/>
</logger>

中文乱码

1
2
3
4
<encoder>
<pattern>...</pattern>
<charset>UTF-8</charset> <!-- 显式指定编码 -->
</encoder>

动态调整日志级别(运行时)

1
2
3
// 通过 JMX 或代码动态调整
LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
ctx.getLogger("com.example").setLevel(Level.DEBUG);

总结

场景推荐方案
本地开发ConsoleAppender + 彩色输出
测试环境文件 + Console,DEBUG 级别
生产环境RollingFileAppender + AsyncAppender,INFO/WARN 级别
分布式追踪MDC 传递 traceId/requestId
审计日志Marker + 单独 Appender
告警通知SMTPAppender 过滤 ERROR

Logback 的优势在于其简洁的设计、高性能和灵活性。掌握它的核心概念(Logger、Appender、Layout/Encoder)和 MDC 特性,你就能构建出既易用又强大的日志系统。

参考资源: