SpringBoot中关于日志的更好使用
# SpringBoot中关于日志的更好使用
# 打日志的重要性
日志,它就像你买了车之后给车上的这个车险,没有人愿意为这个保险付钱,没有人愿意打日志对吧?但是一旦出了问题,谁都又想有保险可用,有日志可循,对不对?
那么为了避免因为没有打日志而造成的各种尴尬,或者出现问题但难以排查,所以我们要做一个善于打日志,且打印姿势完美的程序员。
那怎么掌握打好日志的秘诀呢,除了大量的练习之外,你还需要弄明白这三个问题:
- 首先第一个问题就是我们为什么要打日志
- 第二个问题是什么时候去打日志
- 第三个问题就是如何打日志,也就是说有哪些注意事项
# 一些原因
问题定位与调试
日志是排查线上问题的核心依据。当系统出现异常时,日志能提供错误上下文(如参数、堆栈信息),帮助快速定位问题根源,减少“盲猜”时间。
监控与预警
通过分析日志中的关键指标(如接口耗时、错误频率),可实时监控系统健康状态,触发预警(如Prometheus + Grafana + ELK)。
行为审计与合规
记录用户关键操作(如登录、支付),满足安全审计和合规要求,便于追溯责任。
性能分析
通过日志统计接口耗时、慢查询、线程阻塞等,识别性能瓶颈。
数据恢复
结合日志中的操作流水,可在数据异常时进行恢复(如通过Binlog+日志回放)。
# 什么时候打日志
肯定是在那些你觉得会出现问题的地方去打日志对呀,日志最主要的目的是为了去追溯这个问题的一个原因。当然,问题可以分为错误问题,和性能问题。
如果某个方法报错了,你打了日志,那么你就可以拿到这个日志信息,是不是就可以进行一个排查,或者说进行一个复盘了,对不对?拿到了这个传递的参数,无论你是在本地还是在线上去调试的时候,去追溯的时候,你都可以有一个思路去往下走了,对不对?
# 使用技巧和注意事项
# 日志框架
Spring Boot 默认使用 Logback 作为日志框架(基于 SLF4J 接口)。
- SLF4J 是日志门面,屏蔽底层实现(Logback、Log4j 等)。
- Logback 是性能优秀的日志实现,支持灵活的配置。
# 日志记录规范
推荐使用 SLF4J 的 LoggerFactory
或 Lombok 的 @Slf4j
注解简化代码。也就是使用日志门面
,进行记录日志。
- LoggerFactory
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("/user")
public String getUser() {
log.info("获取用户信息请求");
return "User Info";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- Lombok 的
@Slf4j
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class UserController {
@GetMapping("/user")
public String getUser() {
log.info("获取用户信息请求");
return "User Info";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
为什么要使用日志门面,而不是日志实现?
主要是为了:解耦代码与日志实现
- 问题场景: 若代码直接依赖
Log4j 2
的API,后续想切换为Logback
时,需修改所有日志代码,成本极高。- 日志门面的优势: 代码仅依赖门面接口(如
SLF4J
),底层可自由切换实现(通过更换依赖包和配置文件),无需修改业务代码。 示例:
- 初始使用
SLF4J + Logback
:添加logback-classic
依赖。- 切换为
SLF4J + Log4j 2
:替换为log4j-slf4j-impl
依赖,并配置log4j2.xml
。还一个原因是:避免依赖冲突
- 问题场景: 项目中引入的第三方库可能各自依赖不同的日志实现(如A库用
Log4j 1.x
,B库用JUL
),导致日志混乱或冲突。- 日志门面的解决方案: 通过门面的桥接器(Bridge),统一将不同日志实现的调用转到门面层。 示例配置:
jul-to-slf4j
:将JUL
日志重定向到SLF4J
。log4j-to-slf4j
:将Log4j 2
日志重定向到SLF4J
。- 最终所有日志统一由
SLF4J
门面管理,输出到单一实现(如Logback)。
# 日志级别使用规范
级别 | 用途 |
---|---|
TRACE | 最详细的日志,用于开发调试阶段,生产环境通常关闭。 |
DEBUG | 调试信息,记录程序的详细执行过程(如参数、方法调用)。 |
INFO | 记录应用程序的正常运行状态(如服务启动、请求处理)。 |
WARN | 警告信息,表示潜在的问题(如配置缺失、缓存失效)。 |
ERROR | 错误信息,记录影响功能的异常(如数据库连接失败、接口调用异常)。 |
FATAL | 严重错误,通常会导致应用崩溃(Spring Boot 中默认不启用)。 |
# 在Application文件中配置
以下是一个简单的配置示例:
logging:
level:
root: INFO # 根日志级别
com.example.demo.controller: DEBUG # 指定包的日志级别
file:
name: logs/app.log # 日志文件路径
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
2
3
4
5
6
7
8
如果你觉得这种方式不够自由,可以使用自定义文件的方式进行配置
# 使用配置文件定义日志格式
Spring Boot 会根据 默认的命名规则 自动加载 logback-spring.xml
作为 Logback 的配置文件。如果文件名不是 logback-spring.xml
,你需要通过显式配置来指定日志框架的配置文件位置。
Spring Boot 会自动加载以下文件(按优先级排序):
logback-spring.xml
logback-spring.groovy
logback.xml
logback.groovy
推荐使用 logback-spring.xml
,因为:
- 支持 Spring Boot 的 环境配置(Profile) 和 条件配置。
- 可以使用
<springProfile>
标签实现多环境(dev
/test
/prod
)的日志配置切换。 - 是 Spring Boot 官方推荐的命名方式。
如果想要指定日志配置文件,则可以使用如下方式:
在 application.properties
或 application.yml
中添加以下配置:
# application.yml 示例
logging:
config: classpath:my-logback.xml
# 它等与 properties中的
# 指定自定义的 Logback 配置文件路径
# logging.config=classpath:my-logback.xml
2
3
4
5
6
7
classpath:
表示文件位于src/main/resources
目录下。
以下是一个配置例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 全局日志级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</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
# 注意事项
# 使用面向切面方式
- AOP
使用 AOP 或 Filter 记录用户操作。
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@Before("execution(* com.example.demo.controller.*.*(..))")
public void logRequest(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
log.info("请求方法: {}, URL: {}, 参数: {}", request.getMethod(), request.getRequestURI(), joinPoint.getArgs());
}
}
2
3
4
5
6
7
8
9
10
11
- Filter
@WebFilter("/*")
public class UserOperationLogFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(UserOperationLogFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
log.info("请求方法: {}, URL: {}, 参数: {}", httpRequest.getMethod(), httpRequest.getRequestURI(), httpRequest.getParameterMap());
chain.doFilter(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
# 生产环境关闭 DEBUG/TRACE
生产环境建议将日志级别设置为 INFO
或 WARN
,避免性能开销。
# 避免记录敏感信息
不要直接打印密码、密钥等敏感数据(如 log.info("密码: {}", password)
)
# 异步日志提升性能
使用 AsyncAppender
异步写入日志,减少阻塞:
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
</appender>
2
3
待补充
# 合理选择日志级别
- DEBUG:开发调试用,线上通常关闭。
- INFO:记录关键业务流程(如订单创建成功)。
- WARN:预期内的异常(如参数校验失败),但需关注。
- ERROR:未处理的异常或系统错误(如数据库连接失败)。
// 错误示例:滥用ERROR级别
try {
// 业务逻辑
} catch (ExpectedException e) {
logger.error("Expected error occurred", e); // 应改为WARN
}
2
3
4
5
6
# 性能问题:避免在循环或高频调用中打印
// 反例:高频调用中拼接字符串
logger.debug("User info: " + user.toString()); // 改为占位符方式
2