首页 > 其他分享 >在SpringBoot项目中优雅地记录日志(日志框架选型、SpringBoot默认的日志实现框架、如何使用日志框架、如何切换日志的具体实现框架、与日志相关的配置、记录日志的最佳实践)

在SpringBoot项目中优雅地记录日志(日志框架选型、SpringBoot默认的日志实现框架、如何使用日志框架、如何切换日志的具体实现框架、与日志相关的配置、记录日志的最佳实践)

时间:2024-12-22 19:03:42浏览次数:5  
标签:输出 SpringBoot 框架 配置 6.3 日志 Logback log

文章目录

1. 前言

日志是我们系统出现错误时,最快速有效的定位工具,没有日志给出的错误信息,遇到报错就会一脸懵逼

日志还可以用来记录业务信息,比如记录用户执行的每个操作,不仅可以用于分析改进系统,在遇到非法操作时也能很快找到凶手

对于程序员来说,日志记录是重要的基本功,但很多同学并没有系统学习过日志操作、缺乏使用日志的经验,所以我写下这篇文章,分享自己对日志的一些理解,希望对大家有帮助

日志不是写给机器看的,而是写给未来的你和你的队友看的

2. 日志框架选型

2.1 System.out.println

相信很多同学都是通过 System.out.println 输出信息来调试程序的,但是 System.out.println 存在很严重的问题:

  1. System.out.println 是一个同步方法每次调用都会导致 I/O 操作,比较耗时,频繁使用甚至会严重影响应用程序的性能,不建议在生产环境使用
  2. System.out.println 只能输出简单的信息到标准控制台,无法灵活设置日志级别、格式、输出位置等

我们一般会选择专业的 Java 日志框架或工具库,比如经典的 Apache Log4j 和它的升级版 Log4j 2,还有 Spring Boot 默认集成的 Logback 库

借助专业的 Java 日志框架,我们可以用一行代码快速地完成日志记录,还能灵活调整格式、设置日志级别、将日志写入到文件中、压缩日志等

2.2 SLF4J

SLF4J:Simple Logging Facade for Java

SLF4J 并不是一个具体的日志实现,而是为各种日志框架提供简单统一接口的日志门面(抽象层)

简单来说,我们只需要使用 Slf4j 提供的抽象方法,就能够实现记录日志的功能,日志框架底层的具体实现取决于引入的 jar 包和配置

在这里插入图片描述

2.2.1 Log4j(已停止维护,不再介绍)

Log4j(已停止维护,不再介绍)

2.2.2 LogBack&Log4j2

  • 从稳定性来说,虽然这些 LogBack 和 Log4j2 都被曝出过漏洞,但 Log4j2 的漏洞更为致命
  • 从易用性来说,二者差不多,但 Logback 是 SLF4J 的原生实现、Log4j2 需要进行额外的配置

Spring Boot 默认集成了 Logback,如果没有特殊需求,更推荐初学者选择 Logback

2.3 扩展:日志框架背后的故事

俄罗斯程序员 Ceki 在 2001 年的时候写下了 Log4j 日志框架,慢慢地被很多人熟知

在这里插入图片描述

时机成熟之后,作者将 Log4j 捐献给了 Apache 软件基金会,本来应该是一个双赢的结果,但 Apache 软件基金会并没有将 Log4j 管理得很好,于是 Ceki 就不再维护 Log4j,直接开发出了更好用的 Logback,并写了一个日志的门面框架——Slf4j

在这里插入图片描述

Logback 的出现让 Log4j 慢慢变得没人使用,于是 Apache 软件基金会慢慢停止更新 Log4j

通过改进 Log4j 的缺点,参考 Logback 的优点,Apache 基金会开发出了 Log4j2

3. SpringBoot默认的日志实现框架(Logback)

我们在 IDEA 中打开项目的 pom.xml 文件,右键打开菜单,查看依赖关系图

在这里插入图片描述

按下 CTRL + F 快捷键,搜索 logging 关键字

在这里插入图片描述

可以发现,SpringBoot 最基础的场景启动器就包括了日志的场景启动器(CTRL + 鼠标滚轮 可以缩放图表,长按鼠标右键可以移动视图)

在这里插入图片描述

日志的场景启动器选择 Logback 作为日志实现框架

在这里插入图片描述

4. 如何使用日志框架

日志框架的使用非常简单,一般是先获取到 Logger 日志对象,然后调用 log.xxx(比如 log.debug、log.info、log.error)就能输出日志了

4.1 常规方法

常规方法是通过 LoggerFactory 手动获取 Logger,示例代码如下

在这里插入图片描述

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class LogbackController {

    private static final Logger log = LoggerFactory.getLogger(LogbackController.class);

    @GetMapping("testLogback")
    public void testLogback() {
        log.info("testLogback");
    }

}

上述代码中,我们通过调用日志工厂并传入当前类,创建了一个 Logger 对象,但由于每个类的类名都不同,我们又经常复制这行代码到不同的类中,就很容易忘记修改类名

所以我们可以使用 this.getClass 动态获取当前类的实例,来创建 Logger 对象(此时的 Logger 对象不能再用 static 修饰

在这里插入图片描述

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class LogbackController {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @GetMapping("testLogback")
    public void testLogback() {
        log.error("testLogback");
    }

}

给每个类都复制一遍这行代码,就能愉快地打日志了

但这样做还是有点麻烦,大部分同学连复制粘贴都懒得做,怎么办

4.2 使用 Lombok 工具库提供的 @Slf4j 注解

还有更简单的方式,使用 Lombok 工具库提供的 @Slf4j 注解,可以自动为当前类生成一个名为 log 的 SLF4J Logger 对象,简化了 Logger 的定义过程,示例代码如下

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
@Slf4j
public class LogbackController {

    @GetMapping("testLogback")
    public void testLogback() {
        log.error("testLogback");
    }

}

4.3 @Slf4j注解的原理

其实 @Slf4j 注解本质上用的还是常规方法,在 target 目录下生成的字节码中可以找到答案

在这里插入图片描述

5. 切换日志的具体实现框架(从Logback切换成Log4j2)

Logback 和 Log4j2 是市面上性能较好的两个日志框架,所以本次演示是从 Logback 切换成 Log4j2

5.1 排除掉与Logback相关的依赖

在这里插入图片描述

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-logging</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

5.2 添加Log4j2的场景启动器

Spring 官方已经为我们提供了 Log4j2 的场景启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

6. 在SpringBoot项目中如何编写与日志相关的配置

下面以 Logback 为例,演示在 SpringBoot 项目中如何编写与日志相关的配置

6.1 通过application.yml文件配置(只能编写简单的配置,不推荐使用)

application.yml 文件中配置以下内容

# 日志配置
logging:
  level:
    root: INFO # 设置根日志级别
    cn.edu.scau: INFO # 为你的包或类设置特定的日志级别
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" # 控制台日志的输出格式
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" # 文件日志的输出格式
  file:
    path: logs # 日志文件存储路径, 如果不指定,默认为当前目录
    name: ${logging.file.path}/app.log # 日志文件名
  #  config: classpath:logback-spring.xml # 如果需要,可以指定一个Logback配置文件
  # 配置Logback的滚动策略
  logback:
    rollingpolicy:
      max-file-size: 10MB # 单个日志文件的最大大小
      max-history: 30 # 保留的日志文件的最大数量
      clean-history-on-start: false # 应用启动时是否清理旧的日志文件
      file-name-pattern: app-%d{yyyy-MM-dd}.%i.log # 日志文件名格式,包括日期和索引

在这里插入图片描述

6.2 通过logback.xml配置文件配置(能够编写详细的配置,推荐使用)

logback.xml 配置文件存放在 resources 目录下,用于配置与日志相关的详细信息

6.3 logback.xml文件(logback-spring.xml文件)详解

logback.xml 文件的配置比较复杂,不需要特别记忆,需要用到的时候再查询就可以了

6.3.1 configuration标签

整个配置文件的父标签,无特别含义

6.3.2 include标签

可以引入其它配置文件,例如引入 Spring 提供的 Logback 的默认配置文件

<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

6.3.3 property标签

用于定义变量,定义的变量可以在配置文件的其它地方直接使用

在这里插入图片描述

<!-- 定义应用程序名称 -->
<property name="APP_NAME" value="YOUR_APP_NAME"/>
<!-- 定义日志文件的存储路径 -->
<property name="LOG_FILE_PATH" value="logs"/>
<!-- 定义日志文件的文件名,通过拼接日志文件的存储路径、应用程序名称、log扩展名来创建完整的日志文件 -->
<property name="LOG_FILE_NAME" value="${LOG_FILE_PATH}/${APP_NAME}.log"/>

6.3.4 appender标签

通过 appender 标签可以

  1. 定义日志输出位置:通过配置不同的appender,你可以指定日志信息应该被发送到哪里。例如,你可以配置一个ConsoleAppender来在控制台输出日志,或者配置一个FileAppender来将日志写入文件
  2. 设置日志格式:每个appender都可以有一个与之关联的<encoder>,用于定义日志的格式。这包括日志消息的布局、日期格式等
  3. 过滤日志事件:Appender可以包含<filter>标签,用于根据特定的条件过滤日志事件。例如,你可以设置只记录特定级别的日志或者排除某些特定的日志消息
  4. 设置日志滚动策略:对于文件输出,appender可以配置滚动策略(如RollingFileAppender),这决定了何时和如何创建新的日志文件。例如,基于文件大小(SizeBasedTriggeringPolicy)或时间(TimeBasedRollingPolicy
6.3.4.1 控制台输出配置

在这里插入图片描述

<!-- Console 输出配置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 编码器配置,用于格式化日志输出 -->
    <encoder>
        <!-- 定义日志消息的输出格式,包括日期、进程ID、线程名、日志级别、日志器名和日志消息 -->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%processId] [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>
6.3.4.2 文件输出配置

在这里插入图片描述

<!-- File 输出配置 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 编码器配置,用于格式化日志输出 -->
    <encoder>
        <!-- 定义日志消息的输出格式,包括日期、进程ID、线程名、日志级别、日志器名和日志消息 -->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%processId] [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
    <!-- 滚动策略配置,基于时间的滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- 定义滚动日志文件的命名模式,文件名包含日期,并且每天生成一个新的日志文件 -->
        <fileNamePattern>${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
    </rollingPolicy>
</appender>
6.3.4.3 异步输出配置

在这里插入图片描述

<!-- 异步输出配置 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 队列大小,定义了在内存中存储日志事件的队列的最大容量 -->
    <queueSize>512</queueSize> <!-- 队列大小 -->

    <!-- 丢弃阈值,当队列剩余容量小于这个值时,将丢弃低于指定级别的日志事件 -->
    <!-- 0 表示不丢弃任何级别的日志事件 -->
    <discardingThreshold>0</discardingThreshold> <!-- 丢弃阈值,0 表示不丢弃 -->

    <!-- 队列满时是否阻塞主线程,true 表示即使队列满了也不会阻塞主线程 -->
    <!-- 这可能会导致日志事件丢失 -->
    <neverBlock>true</neverBlock> <!-- 队列满时是否阻塞主线程,true 表示不阻塞 -->

    <!-- 指定异步日志输出要引用的appender,此处只能引用一个appender,如果引用了多个appender,只有第一个appender生效 -->
    <!-- 异步appender会将日志事件转发给这里指定的appender -->
    <appender-ref ref="FILE"/> <!-- 生效的日志目标 -->
</appender>

neverBlock 设置为 true 时,即使队列满了,AsyncAppender 也不会阻塞主线程。相反,它会丢弃新的日志事件,并且通常会记录一条警告信息,表明有日志事件因为队列满而被丢弃。以下是这种行为可能导致日志事件丢失的原因:

  1. 队列满:如果队列满了,而新的日志事件继续产生,AsyncAppender 会根据 neverBlock 的设置来决定是否丢弃这些事件
  2. 不阻塞主线程:当 neverBlock 设置为 true 时,如果队列满了,AsyncAppender 会选择不阻塞主线程,而是直接丢弃新的日志事件
  3. 性能考虑:在某些情况下,应用程序的性能比日志完整性更重要。例如,在极端负载情况下,为了保持应用程序的响应性,可能会选择丢弃一些日志事件

如果你不希望丢失任何日志事件,应该将 neverBlock 设置为 false(或者不设置,因为这是默认值)。但是,请注意,这可能会在日志队列满时导致主线程阻塞,从而影响应用程序的整体性能

6.3.5 root标签

在这里插入图片描述

<!-- 设置日志级别为INFO,即INFO级别及以上的日志事件才会被处理 -->
<root level="INFO">
    <!-- 引用名为CONSOLE的appender,将日志输出到控制台 -->
    <appender-ref ref="CONSOLE"/>

    <!-- 引用名为FILE的appender,将日志输出到文件 -->
    <!--<appender-ref ref="FILE"/>-->

    <!-- 引用名为ASYNC的异步appender,提高日志记录性能 -->
    <appender-ref ref="ASYNC"/>
</root>

6.3.6 logger标签

logger 标签可以精确到类或者包的日志输出级别,优先级最高

在这里插入图片描述

<!-- 配置特定包的日志级别 -->
<!-- 设置cn.edu.scau包下的所有类的日志级别为INFO -->
<logger name="cn.edu.scau" level="info"/>

<!-- 配置特定类的日志级别为WARN -->
<!-- 这将覆盖默认的日志级别设置,只记录WARN及以上级别的日志 -->
<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>

6.4 完整的logback.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<configuration debug="true">
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <!-- 定义应用程序名称 -->
    <property name="APP_NAME" value="YOUR_APP_NAME"/>
    <!-- 定义日志文件的存储路径 -->
    <property name="LOG_FILE_PATH" value="logs"/>
    <!-- 定义日志文件的文件名,通过拼接日志文件的存储路径、应用程序名称、log扩展名来创建完整的日志文件 -->
    <property name="LOG_FILE_NAME" value="${LOG_FILE_PATH}/${APP_NAME}.log"/>

    <!-- Console 输出配置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 编码器配置,用于格式化日志输出 -->
        <encoder>
            <!-- 定义日志消息的输出格式,包括日期、进程ID、线程名、日志级别、日志器名和日志消息 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- File 输出配置 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 编码器配置,用于格式化日志输出 -->
        <encoder>
            <!-- 定义日志消息的输出格式,包括日期、进程ID、线程名、日志级别、日志器名和日志消息 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 滚动策略配置,基于时间的滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 定义滚动日志文件的命名模式,文件名包含日期,并且每天生成一个新的日志文件 -->
            <fileNamePattern>${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>

    </appender>

    <!-- 异步输出配置 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 队列大小,定义了在内存中存储日志事件的队列的最大容量 -->
        <queueSize>512</queueSize> <!-- 队列大小 -->

        <!-- 丢弃阈值,当队列剩余容量小于这个值时,将丢弃低于指定级别的日志事件 -->
        <!-- 0 表示不丢弃任何级别的日志事件 -->
        <discardingThreshold>0</discardingThreshold> <!-- 丢弃阈值,0 表示不丢弃 -->

        <!-- 队列满时是否阻塞主线程,true 表示即使队列满了也不会阻塞主线程 -->
        <!-- 这可能会导致日志事件丢失 -->
        <neverBlock>true</neverBlock> <!-- 队列满时是否阻塞主线程,true 表示不阻塞 -->

        <!-- 指定异步日志输出要引用的appender,此处只能引用一个appender,如果引用了多个appender,只有第一个appender生效 -->
        <!-- 异步appender会将日志事件转发给这里指定的appender -->
        <appender-ref ref="FILE"/> <!-- 生效的日志目标 -->
    </appender>

    <!-- 设置日志级别为INFO,即INFO级别及以上的日志事件才会被处理 -->
    <root level="INFO">
        <!-- 引用名为CONSOLE的appender,将日志输出到控制台 -->
        <appender-ref ref="CONSOLE"/>

        <!-- 引用名为FILE的appender,将日志输出到文件 -->
        <!--<appender-ref ref="FILE"/>-->

        <!-- 引用名为ASYNC的异步appender,提高日志记录性能 -->
        <appender-ref ref="ASYNC"/>
    </root>

    <!-- 配置特定包的日志级别 -->
    <!-- 设置cn.edu.scau包下的所有类的日志级别为INFO -->
    <logger name="cn.edu.scau" level="INFO"/>

    <!-- 配置特定类的日志级别为WARN -->
    <!-- 这将覆盖默认的日志级别设置,只记录WARN及以上级别的日志 -->
    <logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>

</configuration>

6.5 补充:如何禁止控制台输出日志

application.yml 配置文件只能简单地设置日志的输出级别和输出路径,禁止输出到控制台只能在 logback.xml 中进行配置

在 root 标签中排除掉 CONSOLE 标签就可以了

在这里插入图片描述

6.6 补充:SizeAndTimeBasedRollingPolicy 策略

SizeAndTimeBasedRollingPolicy 是基于时间和日志文件大小的滚动策略

在这里插入图片描述

<!-- 滚动策略配置,基于时间和日志文件大小的滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <!-- 定义滚动日志文件的命名模式,文件名包含日期,并且每天生成一个新的日志文件 -->
    <fileNamePattern>${LOG_FILE_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <maxHistory>30</maxHistory>
    <maxFileSize>64MB</maxFileSize>
    <totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>

如果 LOG_FILE_NAME 变量的值为 test,那么最新的日志都会被记录到 test.log 文件中,历史日志记录的格式如下

在这里插入图片描述

如果某一天的日志量很大,最后一位数字会递增

在这里插入图片描述

6.7 使用异步日志的注意事项

如果开启了异步输出,就不需要在 root 标签中添加 FILE appender 了,因为 ASYNC appender 已经与 FILE appender 产生关联

如果既开启了异步输出,root 标签中又添加 FILE appender,那么同一行日志在日志文件中将会被记录两次

在这里插入图片描述

7. 补充:Logback配置文件的命名规范

LogbackLoggingSystem

LogbackLoggingSystem 类的 getStandardConfigLocations 方法中,定义了配置文件的四种命名

在这里插入图片描述


AbstractLoggingSystem

查看 AbstractLoggingSystem 类的 getSpringConfigLocations 方法的源码,可以看到,如果是在 Spring 项目中,还可以添加 -spring 后缀

在这里插入图片描述

配置文件的命名符合以下八个中的其中一个就行了

建议配置文件的命名使用 logback.xml 或 logback-spring.xml

在这里插入图片描述

8. 记录日志的最佳实践

8.1 合理选择日志输出级别

Logback 提供了多个日志输出级别,按照严重性递增的顺序排列如下:

日志级别描述
TRACE提供程序执行过程中的详细信息,通常用于开发阶段,帮助开发者追踪程序的执行流程
DEBUG提供更详细的诊断信息,用于在开发过程中定位问题,通常不会在生产环境中启用
INFO记录应用程序的运行状态,包括关键操作的信息,这些日志通常对最终用户或管理员有所帮助
WARN表明可能发生了潜在错误,程序仍能继续运行,但需要注意这些警告信息
ERROR记录应用程序运行中发生的错误,这些错误可能会导致程序部分功能失效,但不会导致整个程序崩溃
OFF完全禁用日志记录,不输出任何日志信息

建议在开发环境使用低级别日志(比如 DEBUG),以获取详细的信息


在生产环境中,通常会配置日志级别为 INFOWARN

  • INFO 级别用于记录应用程序的运行状态和关键操作的信息,可以帮助运维人员了解应用程序的运行情况,同时也为后续的问题排查提供必要的信息
  • WARN 级别用于记录潜在的问题或者需要注意的情况,这些信息可能不会立即影响应用程序的运行,表明可能存在需要调查和解决的问题

8.2 正确地记录日志信息

当要输出的日志内容中存在变量时,建议使用参数化日志,也就是在日志信息中使用占位符( {}),由日志框架在运行时替换为实际的参数值

比如输出一行用户登录的日志

// 不推荐
log.debug("用户ID:" + userId + " 登录成功。");

// 推荐
log.debug("用户ID:{} 登录成功。", userId);

这样做不仅让日志清晰易读,而且在日志级别低于当前记录级别时,不会执行字符串拼接,从而避免了字符串拼接带来的性能开销以及潜在的 NullPointerException 问题

所以建议在所有日志记录中,使用参数化的方式替代字符串拼接


此外,在输出异常信息时,建议同时记录上下文信息、以及完整的异常堆栈信息,便于排查问题

try {
    // 业务逻辑
} catch (Exception e) {
	logger.error("处理用户ID:{} 时发生异常:", userId, e);
}

8.3 控制日志的输出量

过多的日志不仅会占用更多的磁盘空间,还会增加系统的 I/O 负担,影响系统性能

因此,除了根据环境设置合适的日志级别外,还要尽量避免在循环中输出日志


在循环中利用 StringBuilder 进行字符串拼接,循环结束后统一输出

StringBuilder logBuilder = new StringBuilder("处理结果:").append(System.lineSeparator());

for (Item item : items) {
    try {
        processItem(item);
        logBuilder.append(String.format("成功[ID=%s]%s", item.getId(), System.lineSeparator()));
    } catch (Exception e) {
        logBuilder.append(String.format("失败[ID=%s, 原因=%s]%s", item.getId(), e.getMessage(), System.lineSeparator()));
    }
}

log.info(logBuilder.toString());

如果参数的计算开销较大,且当前日志级别不需要输出,应该在记录前进行级别检查,从而避免多余的参数计算

if (log.isDebugEnabled()) {
    log.debug("复杂对象信息:{}", expensiveToComputeObject());
}

此外,还可以通过更改日志配置文件整体过滤掉特定级别的日志,来防止日志刷屏

<!-- Logback 示例 -->
<appender name="LIMITED" class="ch.qos.logback.classic.AsyncAppender">
  	<!-- 只允许 INFO 级别及以上的日志通过 -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>INFO</level>
    </filter>
    <!-- 配置其他属性 -->
</appender>

8.4 把控记录日志的时机和日志的内容

注意不要在日志中记录了敏感信息,万一你的日志不小心泄露出去,就相当于泄露了大量用户的信息

很多开发者(尤其是线上经验不丰富的开发者)并没有养成记录日志的习惯,觉得记录日志不重要,等到出了问题无法排查的时候才追悔莫及

  1. 在系统的关键流程和重要业务节点记录日志,比如用户登录、订单处理、支付等都是关键业务,建议多记录日志

  2. 对于重要的方法,建议在入口和出口记录重要的参数和返回值,便于快速还原现场、复现问题

  3. 对于调用链较长的操作,确保在每个环节都有日志,以便追踪到问题所在的环节

8.5 日志管理

随着日志文件的持续增长,会导致磁盘空间耗尽,影响系统正常运行,所以我们需要一些策略来对日志进行管理

首先是设置日志的滚动策略,可以根据文件大小或日期,自动对日志文件进行切分

8.5.1 按文件大小滚动

<!-- 按大小滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeBasedRollingPolicy">
    <maxFileSize>10MB</maxFileSize>
</rollingPolicy>

如果日志文件大小达到 10MB,Logback 会将当前日志文件重命名为 app.log.1 或其他命名(具体由文件名模式决定),然后创建新的 app.log 文件继续写入日志

8.5.2 按照时间日期滚动

<!-- 滚动策略配置,基于时间的滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 定义滚动日志文件的命名模式,文件名包含日期,并且每天生成一个新的日志文件 -->
    <fileNamePattern>${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>

上述配置表示每天创建一个新的日志文件,%d{yyyy-MM-dd} 表示按照日期命名日志文件

还可以通过 maxHistory 属性,限制保留的历史日志文件数量或天数

<maxHistory>30</maxHistory>

这样一来,我们就可以按照天数查看指定的日志,单个日志文件也不会很大,提高了日志检索效率

8.5.3 日志压缩

对于用户较多的企业级项目,日志的增长是飞快的,可以开启日志压缩功能,节省磁盘空间

<!-- 滚动策略配置,基于时间的滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 定义滚动日志文件的命名模式,文件名包含日期,并且每天生成一个新的日志文件 -->
    <fileNamePattern>${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
</rollingPolicy>

上述配置表示:每天生成一个新的日志文件,旧的日志文件会被压缩存储

8.6 统一日志格式

统一的日志格式有助于日志的解析、搜索和分析,特别是在分布式系统中


统一的日志格式(整齐清晰,支持按照时间、线程、级别、类名和内容搜索)

2024-11-21 14:30:15.123 [main] INFO  com.example.service.UserService - 用户ID:12345 登录成功
2024-11-21 14:30:16.789 [main] ERROR com.example.service.UserService - 用户ID:12345 登录失败,原因:密码错误
2024-11-21 14:30:17.456 [main] DEBUG com.example.dao.UserDao - 执行SQL:[SELECT * FROM users WHERE id=12345]
2024-11-21 14:30:18.654 [main] WARN  com.example.config.AppConfig - 配置项 `timeout` 使用默认值:3000ms
2024-11-21 14:30:19.001 [main] INFO  com.example.Main - 应用启动成功,耗时:2.34秒

不统一的日志格式(看到后直接原地爆炸)

2024/11/21 14:30 登录成功 用户ID: 12345
2024-11-21 14:30:16 错误 用户12345登录失败!密码不对
DEBUG 执行SQL SELECT * FROM users WHERE id=12345
Timeout = default
应用启动成功

建议每个项目都要明确约定和配置一套日志输出规范,确保日志中包含时间戳、日志级别、线程、类名、方法名、消息等关键信息

<!-- Console 输出配置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 编码器配置,用于格式化日志输出 -->
    <encoder>
        <!-- 定义日志消息的输出格式,包括日期、进程ID、线程名、日志级别、日志器名和日志消息 -->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

8.7 使用异步日志

对于追求性能的操作,可以使用异步日志,将日志的写入操作放在单独的线程中,减少对主线程的阻塞,从而提升系统性能

配置的关键是配置缓冲队列,要设置合适的队列大小和丢弃策略,防止日志积压或丢失

<!-- 异步输出配置 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 队列大小,定义了在内存中存储日志事件的队列的最大容量 -->
    <queueSize>512</queueSize> <!-- 队列大小 -->

    <!-- 丢弃阈值,当队列剩余容量小于这个值时,将丢弃低于指定级别的日志事件 -->
    <!-- 0 表示不丢弃任何级别的日志事件 -->
    <discardingThreshold>0</discardingThreshold> <!-- 丢弃阈值,0 表示不丢弃 -->

    <!-- 队列满时是否阻塞主线程,true 表示即使队列满了也不会阻塞主线程 -->
    <!-- 这可能会导致日志事件丢失 -->
    <neverBlock>true</neverBlock> <!-- 队列满时是否阻塞主线程,true 表示不阻塞 -->

    <!-- 指定异步日志输出要引用的appender,此处只能引用一个appender,如果引用了多个appender,只有第一个appender生效 -->
    <!-- 异步appender会将日志事件转发给这里指定的appender -->
    <appender-ref ref="FILE"/> <!-- 生效的日志目标 -->
</appender>

9. 参考文章&参考视频

9.1 参考文章

9.2 参考视频

标签:输出,SpringBoot,框架,配置,6.3,日志,Logback,log
From: https://blog.csdn.net/m0_62128476/article/details/144361802

相关文章

  • springboot校园兼职平台-计算机设计毕业源码26261
    摘要校园兼职平台作为连接学生和校园兼职资源的重要桥梁,具有推动校园就业服务和学生职业发展的重要作用。本项目旨在基于SpringBoot后端框架和Vue前端框架,设计和实现一个高效、便捷的校园兼职平台。通过该平台,学生可以轻松浏览、搜索和申请各类校园兼职岗位,实现校园资源的最......
  • springboot高校计算机专业学习资料共享平台-计算机设计毕业源码24752
    高校计算机专业学习资料共享平台的设计与实现摘 要在信息化、数字化的时代背景下,教育资源的共享与高效利用已成为推动教育现代化的关键。高校作为培养未来人才的重要基地,其计算机专业的学习资料共享显得尤为重要。这些资料不仅涵盖了基础理论知识,还涉及前沿技术、实践项目......
  • springboot个人健康信息管理小程序-计算机设计毕业源码07695
    摘要在当今这个数字化、信息化的时代,个人健康管理已成为人们生活中不可或缺的一部分。随着生活节奏的加快,越来越多的人开始关注自己的身体状况,希望能够及时了解并调整自己的生活习惯,以达到最佳的健康状态。为此,我们开发了一款基于SpringBoot的个人健康信息管理小程序,旨在为......
  • Flask框架入门与实战
    Flask是一个轻量级的Web应用框架,它是基于Python编写的,拥有简单易用、灵活且可扩展的特点。由于Flask的核心功能非常简洁,且提供了丰富的扩展功能,它成为了开发Web应用的流行选择,尤其适合小型应用和微服务开发。本文将介绍Flask的基本使用、功能以及如何通过一个小项目快速上手......
  • springboot-Java搭建的后端服务器返回前端请求结果
    访问spring.io,在上方的projects找到springInitializr,配置如下:点击下方的GENERATE下载。解压到你的workspace文件夹,然后将该位置复制,IDEA中点击左上角->打开,粘贴文件地址,选中springboottest根目录,确定。此时点信任,信任该文件夹,打开新窗口。还是左上角->setting,搜索Maven,配置......
  • springboot毕设网上评教系统论文+程序+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景随着信息技术的高速发展,教育领域也在不断进行信息化变革。传统的评教方式往往依赖于纸质问卷等形式,存在诸多弊端,例如数据收集和统计效率低下、信......
  • springboot毕设理发店会员管理系统程序+论文+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景在现代社会,理发店行业竞争日益激烈。随着人们生活水平和消费观念的转变,消费者对于理发服务的要求不仅仅局限于剪发本身,更包括个性化的服务体验、......
  • flask框架家庭智能理财管理毕设源码+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景随着经济的发展和人们生活水平的提高,家庭理财变得日益重要。关于家庭理财管理的研究,现有研究主要以传统理财方式为主,如银行储蓄、股票......
  • springboot毕设 智慧医疗系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,智慧医疗系统已成为现代医疗体系中的重要组成部分。近年来,医疗资源的紧张与民众健康需求的日益增长之间的矛盾日益凸显,传统医......
  • springboot毕设 智能热度分析和自媒体推送平台 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展和社交媒体的广泛普及,自媒体已成为信息传播的重要渠道。每天,海量的文章、图片和视频等内容在各大自媒体平台上发布,用户面对......