tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 探索SpringBoot

  • 常用功能实现

    • SpringBoot常用功能实现_1
    • SpringBoot中拦截器与日志
    • 多环境配置与数据绑定
    • 异步方法(线程池)的实现
    • controller参数接收
    • SpringBoot中关于日志的更好使用
      • 打日志的重要性
        • 一些原因
        • 什么时候打日志
      • 使用技巧和注意事项
        • 日志框架
        • 日志记录规范
        • 日志级别使用规范
        • 在Application文件中配置
        • 使用配置文件定义日志格式
        • 注意事项
        • 使用面向切面方式
        • 生产环境关闭 DEBUG/TRACE
        • 避免记录敏感信息
        • 异步日志提升性能
        • 合理选择日志级别
        • 性能问题:避免在循环或高频调用中打印
    • 异常捕获的一些细节
    • 时间跟其他数据的序列化
    • 面向切面跟自定义注解的结合
  • Security认证授权

  • 扩展

  • 实战与注意事项

  • 其它

  • 《SpringBoot》笔记
  • 常用功能实现
EffectTang
2025-05-07
目录

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";
    }
}
1
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";
    }
}
1
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"
1
2
3
4
5
6
7
8

如果你觉得这种方式不够自由,可以使用自定义文件的方式进行配置

# 使用配置文件定义日志格式

Spring Boot 会根据 默认的命名规则 自动加载 logback-spring.xml 作为 Logback 的配置文件。如果文件名不是 logback-spring.xml,你需要通过显式配置来指定日志框架的配置文件位置。

Spring Boot 会自动加载以下文件(按优先级排序):

  1. logback-spring.xml
  2. logback-spring.groovy
  3. logback.xml
  4. 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
1
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>
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

# 注意事项

# 使用面向切面方式

  • 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());
    }
}
1
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);
    }
}
1
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>
1
2
3

待补充

# 合理选择日志级别

  • DEBUG:开发调试用,线上通常关闭。
  • INFO:记录关键业务流程(如订单创建成功)。
  • WARN:预期内的异常(如参数校验失败),但需关注。
  • ERROR:未处理的异常或系统错误(如数据库连接失败)。
// 错误示例:滥用ERROR级别
try {
    // 业务逻辑
} catch (ExpectedException e) {
    logger.error("Expected error occurred", e); // 应改为WARN
}
1
2
3
4
5
6

# 性能问题:避免在循环或高频调用中打印

// 反例:高频调用中拼接字符串
logger.debug("User info: " + user.toString()); // 改为占位符方式
1
2
上次更新: 2025/05/07, 15:50:59
controller参数接收
异常捕获的一些细节

← controller参数接收 异常捕获的一些细节→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式