日志管理
日志是应用程序运行过程中的重要记录,对于问题排查、性能分析和安全审计至关重要。Spring Boot 默认使用 SLF4J + Logback 组合,提供了完善的日志支持。
日志框架概述
日志门面与实现
Spring Boot 的日志体系采用"门面模式":
┌─────────────────────────────────────────────────────────────┐
│ 日志框架架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 应用代码 ──> SLF4J(日志门面)──> Logback(日志实现) │
│ │ │
│ ▼ │
│ 统一的日志 API │
│ │
│ 其他日志框架的适配: │
│ - Java Util Logging ──> SLF4J 桥接 │
│ - Commons Logging ──> SLF4J 桥接 │
│ - Log4j ──> SLF4J 桥接 │
│ │
└─────────────────────────────────────────────────────────────┘
为什么需要门面?
| 组件 | 作用 |
|---|---|
| SLF4J | 提供统一的日志 API,应用代码只依赖接口 |
| Logback | 具体的日志实现,高性能、功能丰富 |
这样做的好处是:应用代码与具体日志实现解耦,可以随时切换日志框架。
日志级别
日志级别从低到高:
| 级别 | 说明 | 使用场景 |
|---|---|---|
| TRACE | 最详细的信息 | 程序追踪调试 |
| DEBUG | 调试信息 | 开发调试 |
| INFO | 重要信息 | 关键业务流程 |
| WARN | 警告信息 | 潜在问题 |
| ERROR | 错误信息 | 错误但程序可继续 |
| FATAL | 严重错误 | 程序无法继续(Logback 映射为 ERROR) |
| OFF | 关闭日志 | 不输出任何日志 |
使用日志
创建 Logger
方式一:使用 Lombok 注解(推荐)
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UserService {
public User findById(Long id) {
log.info("查询用户,ID: {}", id);
User user = userRepository.findById(id).orElse(null);
if (user == null) {
log.warn("用户不存在,ID: {}", id);
}
return user;
}
}
方式二:手动创建 Logger
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void doSomething() {
log.info("执行操作");
}
}
日志方法
@Service
@Slf4j
public class OrderService {
public void processOrder(Order order) {
// 基本日志
log.trace("追踪信息:{}", order);
log.debug("调试信息:{}", order);
log.info("处理订单:{}", order.getId());
log.warn("库存不足警告:{}", order.getProductId());
log.error("订单处理失败", new RuntimeException("错误原因"));
// 带异常的日志
try {
// 业务逻辑
} catch (Exception e) {
log.error("处理订单异常,订单ID: {}", order.getId(), e);
}
// 条件日志(性能优化)
if (log.isDebugEnabled()) {
log.debug("详细调试信息: {}", expensiveOperation());
}
}
private String expensiveOperation() {
// 耗时操作
return "result";
}
}
占位符使用
// 推荐:使用占位符(性能更好)
log.info("用户 {} 登录成功,IP: {}", username, ipAddress);
// 不推荐:字符串拼接
log.info("用户 " + username + " 登录成功"); // 即使日志级别不输出也会拼接字符串
// 多个参数
log.info("订单 {} 状态变更:{} -> {}", orderId, oldStatus, newStatus);
// 带异常
log.error("处理失败,订单ID: {}", orderId, exception);
日志配置
基本配置
在 application.yml 中配置:
# 日志级别
logging:
level:
root: INFO # 根日志级别
com.example: DEBUG # 特定包的日志级别
org.springframework.web: DEBUG # Spring Web 日志
org.hibernate.SQL: DEBUG # Hibernate SQL 日志
# 日志文件配置
file:
name: logs/myapp.log # 日志文件名(完整路径)
# path: logs # 日志目录(与 name 二选一)
max-size: 10MB # 单个文件最大大小
max-history: 30 # 保留的历史文件数
total-size-cap: 1GB # 总日志文件大小上限
# 日志格式
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"
日志级别配置详解
logging:
level:
root: INFO # 全局默认级别
com.example: DEBUG # 项目代码 DEBUG
com.example.mapper: TRACE # Mapper 层显示 SQL 参数
org.springframework: INFO # Spring 框架 INFO
org.springframework.web: DEBUG
org.springframework.security: DEBUG
org.hibernate: INFO
org.hibernate.SQL: DEBUG # 显示 SQL
org.hibernate.type.descriptor.sql: TRACE # 显示 SQL 参数
日志格式说明
%d{yyyy-MM-dd HH:mm:ss.SSS} - 日期时间
%thread - 线程名
%-5level - 日志级别,左对齐,占5位
%logger{36} - Logger 名称,最长36字符
%msg - 日志消息
%n - 换行
%clr(%5p){cyan} - 彩色输出(控制台)
%X{traceId} - MDC 中的 traceId
完整格式示例:
logging:
pattern:
console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){green} [%clr(%thread){blue}] %clr(%-5level){yellow} %clr(%logger{36}){cyan} - %msg%n"
控制台彩色输出
spring:
output:
ansi:
enabled: always # 总是启用彩色输出
# enabled: detect # 自动检测(默认)
# enabled: never # 禁用
颜色对应关系:
| 日志级别 | 颜色 |
|---|---|
| ERROR | 红色 |
| WARN | 黄色 |
| INFO | 绿色 |
| DEBUG | 绿色 |
| TRACE | 绿色 |
日志文件管理
文件滚动策略
Spring Boot 默认提供滚动日志文件支持:
logging:
file:
name: logs/myapp.log
max-size: 10MB # 单文件最大 10MB
max-history: 30 # 保留 30 天
total-size-cap: 1GB # 总大小上限 1GB
clean-history-on-start: false # 启动时是否清理历史文件
文件命名规则:
- 当前日志:
myapp.log - 历史日志:
myapp.2024-01-15.0.log、myapp.2024-01-15.1.log
按日期滚动
logging:
file:
name: logs/myapp.log
logback:
rollingpolicy:
file-name-pattern: logs/myapp.%d{yyyy-MM-dd}.%i.log
max-history: 30
max-file-size: 10MB
Logback 高级配置
创建 logback-spring.xml
在 src/main/resources/ 目录下创建 logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 从 Spring 配置中读取属性 -->
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="myapp"/>
<springProperty scope="context" name="logPath" source="logging.file.path" defaultValue="logs"/>
<!-- 定义变量 -->
<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>${logPath}/${appName}.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${logPath}/${appName}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 错误日志单独输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/${appName}-error.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${logPath}/${appName}-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 只记录 ERROR 级别 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出(提升性能) -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- 开发环境配置 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example" level="DEBUG"/>
</springProfile>
<!-- 生产环境配置 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example" level="INFO"/>
</springProfile>
</configuration>
Profile 特定配置
使用 <springProfile> 标签针对不同环境配置:
<!-- 开发环境:输出到控制台 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 生产环境:输出到文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
<!-- 多环境匹配 -->
<springProfile name="dev | test">
<!-- dev 或 test 环境生效 -->
</springProfile>
<!-- 排除环境 -->
<springProfile name="!prod">
<!-- 非 prod 环境生效 -->
</springProfile>
日志分组
Spring Boot 支持将多个 Logger 组合在一起统一配置:
logging:
group:
# 自定义分组
service: com.example.user,com.example.order,com.example.product
web: org.springframework.web,org.springframework.boot.web
level:
service: DEBUG # 整个分组设置为 DEBUG
web: INFO
内置分组:
| 分组名 | 包含的 Logger |
|---|---|
| web | org.springframework.web |
| sql | org.springframework.jdbc, org.hibernate.SQL |
MDC(Mapped Diagnostic Context)
MDC 用于在日志中添加上下文信息,如请求 ID、用户 ID 等。
使用 MDC
import org.slf4j.MDC;
@Slf4j
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 添加 MDC 信息
MDC.put("userId", String.valueOf(id));
MDC.put("requestId", UUID.randomUUID().toString());
try {
log.info("查询用户信息");
return userService.findById(id);
} finally {
// 清理 MDC
MDC.clear();
}
}
}
在日志格式中使用 MDC
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] [%thread] %-5level %logger{36} - %msg%n"/>
拦截器自动添加 MDC
@Component
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestId = request.getHeader("X-Request-Id");
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
MDC.put("requestId", requestId);
MDC.put("uri", request.getRequestURI());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
MDC.clear();
}
}
结构化日志
Spring Boot 3.x 支持结构化日志(JSON 格式):
启用 JSON 日志
logging:
structured:
format:
console: ecs # Elastic Common Schema
file: ecs
支持的格式:
| 格式 | 说明 |
|---|---|
| ecs | Elastic Common Schema |
| gelf | Graylog Extended Log Format |
| logstash | Logstash JSON format |
JSON 日志输出示例
{
"@timestamp": "2024-01-15T10:30:00.123Z",
"log": {
"level": "INFO",
"logger": "com.example.UserService"
},
"message": "用户登录成功",
"service": {
"name": "myapp",
"version": "1.0.0"
},
"process": {
"pid": 12345,
"thread": {
"name": "http-nio-8080-exec-1"
}
}
}
日志最佳实践
1. 合理使用日志级别
// ERROR:影响业务正常运行的错误
log.error("订单支付失败,订单ID: {}", orderId, e);
// WARN:潜在问题,不影响系统运行
log.warn("库存不足,商品ID: {}, 剩余: {}", productId, stock);
// INFO:关键业务节点
log.info("用户登录成功,用户名: {}", username);
// DEBUG:调试信息(生产环境关闭)
log.debug("查询条件: {}", condition);
// TRACE:详细追踪(仅开发环境)
log.trace("方法入口,参数: {}", params);
2. 避免敏感信息
// 错误:暴露敏感信息
log.info("用户登录,密码: {}", password);
// 正确:脱敏或排除
log.info("用户登录,用户名: {}", username);
3. 使用占位符而非拼接
// 不推荐:字符串拼接
log.info("处理订单: " + orderId + ", 状态: " + status);
// 推荐:占位符
log.info("处理订单: {}, 状态: {}", orderId, status);
4. 条件日志
// 避免不必要的字符串操作
if (log.isDebugEnabled()) {
log.debug("详细数据: {}", objectMapper.writeValueAsString(data));
}
5. 异常日志包含堆栈
// 正确:包含异常堆栈
log.error("处理订单异常,订单ID: {}", orderId, e);
// 错误:丢失堆栈信息
log.error("处理订单异常: " + e.getMessage());
6. 异步日志提升性能
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
常见问题
1. 日志文件不生成
检查配置:
logging:
file:
name: logs/myapp.log # 确保路径正确
2. 中文乱码
<encoder>
<pattern>...</pattern>
<charset>UTF-8</charset>
</encoder>
3. 日志级别不生效
确保配置的包路径正确:
logging:
level:
com.example: DEBUG # 包路径要与代码包路径一致
4. 日志文件过大
配置滚动策略:
logging:
file:
max-size: 10MB
max-history: 30
total-size-cap: 1GB
小结
本章我们学习了:
- 日志框架:SLF4J + Logback 的架构
- 日志级别:TRACE、DEBUG、INFO、WARN、ERROR 的使用
- 基本配置:日志级别、格式、文件配置
- Logback 高级配置:自定义 appender、滚动策略
- MDC:上下文信息传递
- 结构化日志:JSON 格式输出
- 最佳实践:合理使用级别、避免敏感信息、性能优化
练习
- 配置日志输出到文件,并设置滚动策略
- 创建 logback-spring.xml 实现不同环境不同配置
- 使用 MDC 添加请求 ID 到日志
- 实现错误日志单独输出到文件
- 配置异步日志提升性能