日志记录相关
# 日志记录相关
# 日志级别
日志级别用于确定记录在日志中的消息的重要性和严重性。不同的日志级别对应不同的日志消息类型,通常按照以下常见的级别进行分类(从高到低):
- ERROR(错误): 用于记录严重错误,表示应用程序可能无法继续正常运行。这通常是致命错误,需要立即处理。
- WARN(警告): 用于记录一些不太严重的问题或潜在的问题,这些问题不会导致应用程序停止运行,但可能需要关注和处理。
- INFO(信息): 用于记录一般性的信息性消息,例如应用程序启动、配置信息、重要事件等。这些消息有助于了解应用程序的状态。
- DEBUG(调试): 用于记录详细的调试信息,通常用于开发和调试过程中,以便排查问题。在生产环境中通常会关闭 DEBUG 级别的日志。
- TRACE(跟踪): 最详细的日志级别,通常记录了每个方法的调用和变量值。TRACE 级别通常用于深度调试,对于大多数应用程序来说是不需要的,因为它会生成大量的日志数据。
还有一个更高级别 FATAL(致命),部分框架(如 Log4j)支持,表示极其严重的错误,通常会导致应用程序直接退出。Logback 中没有单独的 FATAL 级别,而是将其归入 ERROR。
在实际应用程序中,可以根据需要使用这些日志级别来记录不同类型的消息。通常,可以配置日志系统,以便在不同环境(例如开发、测试、生产)中使用不同的日志级别,以便更好地管理日志信息。例如,在生产环境中通常会将日志级别设置为 ERROR 或 WARN,以减少日志数据量,而在开发环境中可以使用 DEBUG 或 TRACE 级别以便更好地进行调试。
不同的日志框架(例如 Log4j、Logback、Java 自带的 java.util.logging 等)具有不同的配置方式,用于设置和管理日志级别。
# 常见日志框架对比
Java 生态中日志框架众多,容易混淆。下表对常见的日志框架进行对比:
| 框架 | 类型 | 简介 | 推荐度 |
|---|---|---|---|
| SLF4J | 日志门面 | 简单日志门面(Simple Logging Facade for Java),提供统一 API | ⭐⭐⭐⭐⭐ |
| Logback | 日志实现 | Log4j 作者编写的下一代框架,性能优秀,Spring Boot 默认集成 | ⭐⭐⭐⭐⭐ |
| Log4j 2 | 日志实现 | Apache 基金会出品,支持异步日志,性能极佳 | ⭐⭐⭐⭐ |
| Log4j | 日志实现 | 老牌日志框架,功能完善但性能一般,已被 Logback / Log4j 2 取代 | ⭐⭐ |
| JUL | 日志实现 | Java 自带的 java.util.logging,功能简单,性能一般 | ⭐ |
| JCL(Commons Logging) | 日志门面 | Apache Commons Logging,Spring 早期使用,运行时动态绑定实现 | ⭐⭐ |
核心概念:日志框架分为「门面(Facade)」和「实现」两层。
- 门面(如 SLF4J、JCL):提供统一的日志 API,代码中只面向门面编程,不直接依赖具体实现。
- 实现(如 Logback、Log4j 2):真正负责日志输出的底层框架。
这种设计类似于 JDBC——代码面向
java.sql.Connection接口编程,运行时由具体的数据库驱动提供实现。
# 为什么推荐 SLF4J + Logback?
- 解耦:代码只依赖 SLF4J 接口,切换日志实现无需修改业务代码。
- 性能:Logback 相比 Log4j 性能提升约 10 倍,且初始化阶段更快。
- 功能丰富:支持条件化配置、自动滚动归档、过滤器、JMX 管理等。
- Spring Boot 默认集成:开箱即用,无需额外引入依赖。
⚠️ Log4j 2 的安全提醒:Log4j 2 在 2021 年底曾爆出 CVE-2021-44228(Log4Shell) (opens new window) 严重漏洞,使用时务必升级到
2.17.1及以上版本。
# SLF4J 的使用
# 基本用法
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
// 推荐使用当前类的 Class 对象作为参数
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void getUserById(Long id) {
log.info("查询用户信息, userId={}", id);
try {
// 业务逻辑...
} catch (Exception e) {
log.error("查询用户失败, userId={}", id, e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用 Lombok 简化
如果项目中引入了 Lombok,可以使用 @Slf4j 注解自动生成 log 字段:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserService {
public void getUserById(Long id) {
log.info("查询用户信息, userId={}", id);
}
}
2
3
4
5
6
7
8
9
Maven 依赖:
<!-- SLF4J + Logback(Spring Boot 已默认包含,通常无需手动引入) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <!-- Lombok(可选) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency>1
2
3
4
5
6
7
8
9
10
11
12
# Logback 配置详解
Logback 的配置文件通常命名为 logback-spring.xml(Spring Boot 项目推荐)或 logback.xml,放在 src/main/resources/ 目录下。
# 配置文件结构
Logback 配置文件包含三个核心组件:
| 组件 | 作用 |
|---|---|
| Logger | 日志记录器,绑定日志级别和输出目标 |
| Appender | 日志输出目的地(控制台、文件等) |
| Encoder/Layout | 日志格式化(Pattern、JSON 等) |
# 完整配置示例
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 引入 Spring Boot 默认配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 定义变量 -->
<property name="LOG_PATH" value="logs"/>
<property name="APP_NAME" value="my-app"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<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.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按天滚动,同时限制单文件大小 -->
<fileNamePattern>${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 错误日志单独输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根据环境配置不同级别 -->
<!-- 开发环境 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
</configuration>
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
71
72
# Pattern 格式说明
| 占位符 | 含义 | 示例 |
|---|---|---|
%d | 日志时间 | 2024-08-31 11:24:37.123 |
%thread | 线程名称 | http-nio-8080-exec-1 |
%-5level | 日志级别(左对齐,5 字符宽) | ERROR |
%logger{36} | 日志记录器名称(最长 36 字符) | c.e.s.UserService |
%msg | 日志消息 | 查询用户成功 |
%n | 换行符 | |
%class | 输出日志的类名 | com.example.UserService |
%method | 输出日志的方法名 | getUserById |
%line | 输出日志的行号 | 42 |
%M | 方法名(同 %method) | getUserById |
%X{key} | MDC(Mapped Diagnostic Context)中的值 | userId=12345 |
💡 性能提示:
%class、%method、%line需要通过堆栈信息获取,性能开销较大,生产环境不建议使用。
# Spring Boot 中的日志配置
# application.yml 配置方式
如果不需要复杂的 Logback XML 配置,可以直接在 application.yml 中进行简单配置:
logging:
# 日志级别
level:
root: info
com.example: debug
org.springframework.web: debug
org.hibernate.SQL: debug
# 日志文件配置
file:
name: logs/my-app.log
# 或指定路径(文件名由 spring.app.name 决定)
# path: /var/log
# 日志文件滚动策略
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
total-size-cap: 10GB
file-name-pattern: logs/my-app-%d{yyyy-MM-dd}.%i.log.gz
# 控制台日志格式
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
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
# 按环境区分日志级别
# application-dev.yml(开发环境)
logging:
level:
root: debug
com.example: trace
# application-prod.yml(生产环境)
logging:
level:
root: warn
com.example: info
2
3
4
5
6
7
8
9
10
11
关于 Spring Boot 多环境配置的详细说明,可参考 配置文件详解。
# 日志最佳实践
# 1. 使用占位符,避免字符串拼接
// ❌ 错误:即使日志级别不满足,也会执行字符串拼接
log.debug("用户信息: " + user.toString());
// ✅ 正确:使用占位符,日志级别不满足时不会执行拼接
log.debug("用户信息: {}", user);
2
3
4
5
# 2. 异常日志要打印完整堆栈
// ❌ 错误:丢失堆栈信息
try {
// ...
} catch (Exception e) {
log.error("处理失败: " + e.getMessage());
}
// ✅ 正确:将异常对象作为最后一个参数
try {
// ...
} catch (Exception e) {
log.error("处理失败, userId={}", userId, e);
}
2
3
4
5
6
7
8
9
10
11
12
13
⚠️ 注意:异常对象必须是最后一个参数,且不要再用
{}占位符引用它。
# 3. 日志内容要有上下文信息
// ❌ 错误:日志缺乏上下文,难以定位问题
log.error("查询失败");
// ✅ 正确:包含关键业务参数
log.error("查询用户订单失败, userId={}, orderId={}", userId, orderId);
2
3
4
5
# 4. 敏感信息不要记录日志
// ❌ 错误:密码、身份证号等敏感信息泄露
log.info("用户登录, username={}, password={}", username, password);
// ✅ 正确:脱敏处理
log.info("用户登录, username={}", username);
2
3
4
5
# 5. 避免在循环中打印大量日志
// ❌ 错误:循环中打印日志,可能产生海量日志
for (User user : users) {
log.info("处理用户: {}", user.getName());
}
// ✅ 正确:使用批量处理 + 汇总日志
log.info("开始批量处理用户, 总数={}", users.size());
// ... 批量处理逻辑
log.info("批量处理完成, 成功={}, 失败={}", successCount, failCount);
2
3
4
5
6
7
8
9
# 6. 使用 MDC 添加链路追踪信息
MDC(Mapped Diagnostic Context)可以为每条日志自动添加上下文信息,常用于链路追踪:
import org.slf4j.MDC;
// 在请求入口设置
MDC.put("traceId", UUID.randomUUID().toString());
MDC.put("userId", String.valueOf(userId));
try {
// 业务逻辑,所有日志都会自动带上 traceId 和 userId
log.info("处理请求");
} finally {
// 请求结束后清理
MDC.clear();
}
2
3
4
5
6
7
8
9
10
11
12
13
配合 Pattern 中的 %X{traceId} 即可在日志中输出链路 ID。
# 7. 条件性日志
// 在高频场景下,先判断日志级别再执行耗时操作
if (log.isDebugEnabled()) {
log.debug("复杂对象详情: {}", expensiveToString(obj));
}
2
3
4
# 日志的常见问题与排查
# 1. 日志不输出
可能原因:
- 日志级别配置过高,导致低级别日志被过滤
- Logback 配置文件命名或位置错误(Spring Boot 项目应使用
logback-spring.xml) - 存在多个日志实现冲突
排查方法:
// 打印当前 Logger 的级别
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.getLoggerList().forEach(l -> System.out.println(l.getName() + " => " + l.getLevel()));
2
3
# 2. 日志框架冲突
当项目中同时引入多个日志框架时,可能出现 Class path contains multiple SLF4J bindings 警告。
解决方法:使用 Maven 排除冲突依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除 commons-logging -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
# 3. 日志文件过大
解决方法:
- 配置
RollingFileAppender实现日志滚动 - 设置
maxFileSize(单文件最大大小)和maxHistory(保留天数) - 设置
totalSizeCap(总大小上限),避免磁盘被占满
三个滚动参数的关系:
| 参数 | 限制对象 | 触发动作 |
|---|---|---|
maxFileSize | 单个归档文件大小 | 超过则滚动生成新文件 |
maxHistory | 归档文件保留数量/天数 | 超过则删除最旧的文件 |
totalSizeCap | 所有归档文件总大小 | 超过则删除最旧的文件 |
maxHistory和totalSizeCap是双重限制,哪个先触发就先清理。
totalSizeCap 超过后的行为:
- 会自动删除最旧的归档日志文件(按文件名排序,即日期最早或序号最小的
.gz文件) - 逐个删除,直到总大小降到
totalSizeCap以下 - 清理发生在每次日志滚动时(即新建归档文件时检查),不是实时监控
⚠️ 注意:
totalSizeCap只管归档文件,当前正在写入的活跃日志文件(如my-app.log)不计入此限制,所以实际磁盘占用可能略大于totalSizeCap。
# 4. 异步日志提升性能
在高并发场景下,同步写日志可能成为性能瓶颈。可以使用异步 Appender:
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢弃 WARN/ERROR 级别日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 队列大小 -->
<queueSize>1024</queueSize>
<!-- 引用实际的 Appender -->
<appender-ref ref="FILE"/>
</appender>
2
3
4
5
6
7
8
💡 如果使用 Log4j 2,其原生支持异步日志,性能更优,配置更简单。
# 日志在排查问题中的应用
日志是线上问题排查的核心手段。关于 Linux 环境下查看日志的常用命令(grep、tail、less、zcat 等),可参考 高效查看日志的常用命令详解。
# 常见排查场景
| 场景 | 命令示例 |
|---|---|
| 实时查看错误日志 | tail -F app.log \| grep --line-buffered -i "error" |
| 查看某时间段日志 | sed -n '/2024-08-31 10:00/,/2024-08-31 11:00/p' app.log |
| 统计 ERROR 数量 | grep -c "ERROR" app.log |
| 查看压缩历史日志 | zcat app.log.2024-08-30.gz \| grep "ERROR" |
| 查看某异常堆栈 | grep -A 20 "NullPointerException" app.log |
# 总结
| 要点 | 说明 |
|---|---|
| 日志级别 | ERROR > WARN > INFO > DEBUG > TRACE,按环境配置不同级别 |
| 框架选择 | 推荐 SLF4J(门面)+ Logback(实现),Spring Boot 默认集成 |
| 配置方式 | 简单场景用 application.yml,复杂场景用 logback-spring.xml |
| 占位符 | 使用 {} 占位符,避免字符串拼接 |
| 异常处理 | 异常对象作为最后一个参数,保留完整堆栈 |
| 上下文信息 | 日志中包含关键业务参数,便于定位问题 |
| 敏感信息 | 密码、身份证号等敏感信息禁止记录日志 |
| 性能优化 | 高频场景使用 isDebugEnabled() 判断;高并发使用异步日志 |
| 链路追踪 | 使用 MDC 添加 traceId,便于分布式系统问题追踪 |
| 日志归档 | 配置滚动策略,控制日志文件大小和保留时间 |