一:Spring Boot 日志
1.1 日志概述
日志对我们来说并不陌生,我们可以通过打印日志来发现和定位问题,或者根据日志来分析程序的运行过程,随着项目的复杂度提升,我们对日志的打印也有了更高的需求,而不仅仅是定位排查问题,比如有时需要记录⼀些用户的喜好等等,但是 System.out.print 不能很好的满足我们的需求,此时我们就需要使用⼀些专门日志框架,SpringBoot 内置了日志框架 Slf4j , Spring Boot 项目在启动的时候默认就有日志输出:
1.2 打印日志
打印日志的步骤:
步骤 | 描述 |
---|---|
获取日志对象 | 在程序中获取日志对象,用于记录日志信息。 |
输出日志内容 | 使用日志对象打印需要记录的日志内容。 |
1.2.1 在程序中得到日志对象
在程序中获取日志对象需要使用日志工厂 LoggerFactory,如下代码所示:
private static Logger logger = LoggerFactory.getLogger(LoggerController.class);
名称 | 描述 |
---|---|
Logger | 日志记录器,用于记录日志消息。 |
LoggerFactory | 日志工厂类,用于创建 Logger 实例。 |
getLogger(Class) | 创建并获取与指定类相关的 Logger 实例。Class 要和当前类的名字对应 |
注意:Logger 对象是属于 org.slf4j 包下的, 不要导错包.
1.2.2 使用日志对象打印日志
日志对象的打印方法有很多种,我们可以先使用 info() 方法来输出日志,如下代码所示:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoggerController {
private static Logger logger = LoggerFactory.getLogger(LoggerController.class);
@RequestMapping("/logger")
public String logger(){
logger.info("--------------要输出日志的内容----------------");//记录一条信息级别(INFO)的日志
return "打印日志";
}
}
日志打印效果:
1.3 SLF4J 日志框架介绍
SLF4J 是门面模式的典型应用,与其他日志框架不同,它并不是一个具体的日志实现,而是一个日志框架的抽象层,为日志框架制定了一套统一的规范。因此,SLF4J 本身无法独立使用,需要与具体的日志框架结合使用才能实现日志记录功能。
SLF4J 定义了一套统一的日志操作接口,具体日志框架( Logback、Log4j2、JUL 等)是这些接口的具体实现类,提供实际日志记录功能,可以把他们的关系理解为接口和实现类的关系。
1.4 门面模式
门面模式又称为外观模式, 它提供了⼀个统⼀的接口,用来访问子系统中的⼀群接口,其主要特征是定义了⼀个高层接口,让子系统更容易使用.
门面模式主要包含2种角色:
角色 | 描述 |
---|---|
外观角色 | 门面模式的核心,为客户端提供简化的接口,隐藏系统内部的复杂性。客户端通过外观角色访问系统,无需直接与子系统交互。 |
子系统角色 | 系统内部的实际操作部分,由多个类和组件组成,负责处理具体的业务逻辑和操作。 |
举个例子:在医院看病时,患者可能需要经历挂号、门诊、化验、取药等多个流程,这些流程可能会让患者或家属感到复杂和繁琐。但如果有接待人员统一处理这些事务,患者只需与接待人员沟通,整个流程就会变得更加简单和高效。
1.4.1 门面模式的实现
场景:回家后需要逐一打开各个房间的灯,离开家时又需要逐一关闭各个房间的灯,这样操作会比较麻烦。如果设置一个总开关,通过这个总开关来控制整个屋子的灯,就能让操作变得简单方便。这种场景可以通过门面模式来实现。
public class FacadePatternDemo {
public static void main(String[] args) {
LightFacade lightFacade = new LightFacade();
lightFacade.lightOn();
}
}
/**
* 灯的⻔⾯
*/
class LightFacade{
private Light livingRoomLight = new LivingRoomLight();
private Light hallLight = new HallLight();
private Light diningLight = new DiningLight();
public void lightOn(){
livingRoomLight.on();
hallLight.on();
diningLight.on();
}
public void lightOff(){
livingRoomLight.off();
hallLight.off();
diningLight.off();
}
}
interface Light {
void on();
void off();
}
/**
* 客厅灯
*/
class LivingRoomLight implements Light{
@Override
public void on() {
System.out.println("打开客厅灯");
}
@Override
public void off() {
System.out.println("关闭客厅灯");
}
}
/**
* ⾛廊灯
*/
class HallLight implements Light{
@Override
public void on() {
System.out.println("打开⾛廊灯");
}
@Override
public void off() {
System.out.println("关闭⾛廊灯");
}
}
/**
* 餐厅灯
*/
class DiningLight implements Light{
@Override
public void on() {
System.out.println("打开餐厅灯");
}
@Override
public void off() {
System.out.println("关闭餐厅灯");
}
}
如果不使用门面模式:
public class FacadePatternDemo {
public static void main(String[] args) {
// 每个灯都需要单独控制
Light livingRoomLight = new LivingRoomLight();
Light hallLight = new HallLight();
Light diningLight = new DiningLight();
// 开灯操作
livingRoomLight.on();
hallLight.on();
diningLight.on();
// 假设之后想关灯,还需要一个个关闭
livingRoomLight.off();
hallLight.off();
diningLight.off();
}
}
interface Light {
void on();
void off();
}
/**
* 客厅灯
*/
class LivingRoomLight implements Light{
@Override
public void on() {
System.out.println("打开客厅灯");
}
@Override
public void off() {
System.out.println("关闭客厅灯");
}
}
/**
* ⾛廊灯
*/
class HallLight implements Light{
@Override
public void on() {
System.out.println("打开⾛廊灯");
}
@Override
public void off() {
System.out.println("关闭⾛廊灯");
}
}
/**
* 餐厅灯
*/
class DiningLight implements Light{
@Override
public void on() {
System.out.println("打开餐厅灯");
}
@Override
public void off() {
System.out.println("关闭餐厅灯");
}
}
1.5 SLF4J 框架介绍
SLF4J 就是其他日志框架的门面,SLF4J 可以理解为是提供日志服务的统⼀ API 接口,并不涉及到具体的日志逻辑实现.
1.5.1 不引入日志门面
假设一个项目已经使用了 Log4j 作为日志框架,但此时又引入了一个第三方库,比如 Apache ActiveMQ,而它使用的是 Logback 作为日志框架,这就意味着你的项目中同时存在两个不同的日志框架,它们都有自己的 API 和配置方式。因此,你需要在项目中同时维护这两个日志框架,每个日志框架都要配置自己的日志输出格式、日志级别、日志文件位置等信息。
1.5.2 引入日志门面
日志门面就相当于在项目和日志之间又引入了一个中间层,让项目和日志门面进行交互而不是和两个甚至多个日志来交互,引入日志门面之后,应用程序只需要维护一套日志配置文件,并且不需要关心底层日志框架的实现,也不需要修改应用代码,即使日志框架发生变化。这是因为日志门面统一了应用程序与不同日志框架的交互方式,SLF4J 就是这个日志门面
1.6 日志格式的说明
我们之前打印的日志分别代表什么信息呢?
1.7 日志级别
日志级别表示日志信息所对应问题的严重性,有助于快速筛选出目标日志信息。日志级别从高到低依次为:FATAL、ERROR、WARN、INFO、DEBUG、TRACE。
日志级别 | 描述 |
---|---|
FATAL | 致命信息,表示需要立即处理的系统级错误,会导致程序终止运行。 |
ERROR | 错误信息,表示程序中发生错误,但不影响系统继续运行,可能会导致部分关键功能失效。 |
WARN | 警告信息,表示潜在的问题,不会影响当前使用,但可能引发未来的错误或导致性能下降。 |
INFO | 普通信息,用于记录程序正常运行时的重要事件或操作。 |
DEBUG | 调试信息,用于开发阶段记录程序调试时的重要信息,帮助排查问题。 |
TRACE | 追踪信息,比 DEBUG 粒度更细的日志,记录更详细的事件和执行过程。 |
1.8 日志级别的使用
日志级别是开发人员自己设置的. 开发人员根据自己的理解来判断该信息的重要程度,针对这些级别, Logger 对象分别提供了对应的方法, 来输出日志.
@RequestMapping("/printLog")
public String printLog() {
logger.trace("================= trace ===============");
logger.debug("================= debug ===============");
logger.info("================= info ===============");
logger.warn("================= warn ===============");
logger.error("================= error ===============");
return "打印不同级别的⽇志" ;
}
结果发现只打印了 info、warn 和 error 级别的日志,这与日志级别的配置有关。日志的默认输出级别是 info,因此只会打印等级大于或等于 info 的日志,即 info、warn 和 error。fatal 就没必要打印了,因为是致命信息,系统会立即终止运行。
1.9 日志配置
1.9.1 配置日志级别
日志级别配置只需要在配置文件中设置 “logging.level” 配置项即可,如下所示:
- Properties 配置
logging.level.root: debug
- yml配置
logging:
level:
root: debug
重新运行上述代码, 观察结果:
1.9.2 日志持久化
以上日志默认是输出到控制台的,但在生产环境中,我们通常需要将日志保存下来,以便在出现问题时能够进行追踪和分析。这种将日志长期保存的过程称为日志持久化。日志持久化主要有以下两种方式:
持久化方式 | 描述 |
---|---|
配置日志文件名 | 通过配置日志的文件名,将日志信息保存到指定的文件中。 |
配置日志存储目录 | 通过设置日志的存储路径,将日志文件保存到特定的目录中,便于管理和查找。 |
1.9.2.1 配置日志文件名
- Properties 配置
#日志会保存在这个文件中
logging.file.name: logger/springboot.log
- yml 配置
# 设置⽇志⽂件的⽂件名
logging:
file:
name: logger/springboot.log
运行结果后日志内容保存在了对应的目录下:
1.9.2.2 配置日志的存储目录
- Properties 配置
logging.file.path: D:/temp
- yml 配置
# 设置⽇志⽂件的⽬录
logging:
file:
path: D:/temp
运行程序后该路径下多出⼀个日志⽂件: spring.log
注意:当同时配置 logging.file.name 和 logging.file.path 时,只有 logging.file.name 会生效,logging.file.path 将被忽略。
1.9.3 配置日志文件分割
如果我们的日志都放在⼀个文件中,随着项目的运行日志文件会越来越大,所以我们需要对日志文件进行分割。
- Properties 配置
#这行代码用于配置日志分割后的文件名格式,${LOG_FILE} 是一个占位符,表示日志文件的名称
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
#用于配置一个日志文件的最大大小
logging.logback.rollingpolicy.max-file-size=2KB
- yml 配置
logging:
logback:
rollingpolicy:
max-file-size: 2KB
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
运行项目后多打印⼀些日志,查看日志分割结果:
1.9.4 配置日志格式
当前日志的打印格式为默认格式,但日志格式是支持自定义配置的。我们可以分别为控制台和日志文件设置不同的日志格式,通常通过以下两个配置项来实现:
配置项 | 描述 |
---|---|
logging.pattern.console | 设置控制台日志的打印格式。 |
logging.pattern.file | 设置日志文件的打印格式。 |
1.9.4.1 logging.pattern.console
logging.pattern.console 用于控制台日志格式,它的默认值为:
%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint}
%clr(${LOG_LEVEL_PATTERN:-%5p})
%clr(${PID:- }){magenta}
%clr(---){faint}
%clr([%15.15t]){faint}
%clr(%-40.40logger{39}){cyan}
%clr(:){faint}
%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
配置项及描述 | 详细说明 |
---|---|
%clr( ){ } | 用于给日志内容添加颜色或样式,每个 %clr() 包裹的内容是日志的一部分,后面的 {} 指定颜色或样式,例如:faint(颜色较淡)、magenta(洋红色)、cyan(青色)等。 |
%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd’T’HH:mm:ss.SSSXXX}}){faint} | %d{} 表示日期时间格式化。 ${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd’T’HH:mm:ss.SSSXXX}:从环境变量 LOG_DATEFORMAT_PATTERN 获取格式,未设置时默认使用 yyyy-MM-dd’T’HH:mm:ss.SSSXXX。日期格式说明: - yyyy-MM-dd:年份-月份-日期 - ‘T’:日期和时间分隔符 - HH:mm:ss.SSS:小时、分钟、秒及毫秒 - XXX:时区 {faint} 使用浅色输出。 |
%clr(${LOG_LEVEL_PATTERN:-%5p}) | ${LOG_LEVEL_PATTERN:-%5p}:指定日志级别的输出格式。 - LOG_LEVEL_PATTERN 是环境变量,若未设置则默认使用 %5p,表示日志级别(如 INFO、DEBUG、ERROR 等),宽度为 5 个字符。 |
%clr(${PID:- }){magenta} | ${PID:- }:进程 ID,若未设置 PID,则输出空格。 {magenta} 使用洋红色输出这部分内容。 |
%clr(—){faint} | —:分隔符,用于美化日志输出。 {faint} 使用浅色输出分隔符。 |
%clr([%15.15t]){faint} | %t 表示当前执行线程的名称。 %15.15t:线程名称最多显示 15 个字符,超出部分会截断,不足则填充空格。 {faint} 使用浅色输出线程名称。 |
%clr(%-40.40logger{39}){cyan} | %-40.40logger{39}:日志记录器的名称(通常是类名)。 - logger{39}:记录器名称最多显示 39 个字符。 - %-40.40:最少占用 40 个字符,左对齐,超出部分截断,不足部分填充空格。 {cyan} 用青色输出记录器名称。 |
%clr( : ){faint} %m%n | : 分隔日志记录器名称和实际日志内容。 {faint} 使用浅色输出冒号。 %m:日志消息内容。 %n:换行符。 |
${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} | 配置异常信息的输出格式。 LOG_EXCEPTION_CONVERSION_WORD 是环境变量,允许自定义异常格式。 未设置时,默认使用 %wEx,表示输出完整的异常信息(包括堆栈跟踪)。 |
1.9.4.2 logging.pattern.file
logging.pattern.file 用于控制日志文件的日志格式,它的默认值为:
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}
${LOG_LEVEL_PATTERN:-%5p}
${PID:- } --- [%t]
%-40.40logger{39} :
%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
配置项 | 描述 |
---|---|
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd’T’HH:mm:ss.SSSXXX}} | %d{} 用于格式化日期和时间。 ${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd’T’HH:mm:ss.SSSXXX}:从环境变量 LOG_DATEFORMAT_PATTERN 获取日期格式,若未设置,则使用默认格式 yyyy-MM-dd’T’HH:mm:ss.SSSXXX。 日期格式说明: - yyyy-MM-dd:年份-月份-日期 - ‘T’:日期和时间分隔符 - HH:mm:ss.SSS:小时、分钟、秒和毫秒 - XXX:时区信息(例如 +08:00)。 |
${LOG_LEVEL_PATTERN:-%5p} | ${LOG_LEVEL_PATTERN:-%5p} 用于指定日志级别的输出格式。 - 尝试从环境变量 LOG_LEVEL_PATTERN 获取日志级别格式,若未设置,则使用默认格式 %5p。 - %5p:表示日志级别(如 INFO、DEBUG、ERROR),占用 5 个字符宽度,方便对齐显示。 |
${PID:- }—[%t] | ${PID:- }:从环境变量 PID 获取进程 ID,若未设置则输出空格。 —:固定分隔符,用于美化日志输出。 [%t]:表示当前线程的名称,输出时用方括号包裹(例如 [main])。 |
%-40.40logger{39} | %-40.40logger{39}:指定日志记录器名称的格式。 - logger{39}:记录器名称最多显示 39 个字符,超出部分会被截断。 - %-40.40:表示记录器名称固定占用 40 个字符:左对齐,名称不足补充空格,超出部分截断。 |
: | 分隔符,用于分隔日志记录器名称和日志消息,方便日志的结构化显示。 |
%m%n | %m:日志的具体消息内容。 %n:换行符,用于每条日志消息结束后自动换行,便于日志分行显示。 |
${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} | ${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}:配置异常信息的输出格式。 - 尝试从环境变量 LOG_EXCEPTION_CONVERSION_WORD 获取格式,若未设置,则默认使用 %wEx。 - %wEx:输出完整的异常堆栈跟踪信息,包括异常类名、异常消息和调用链。 |
1.9.5 修改日志的默认格式
下面举一个简单的代码例子来修改日志的默认格式
- Properties 配置
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
- yml 配置
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
配置项 | 描述 |
---|---|
%d{yyyy-MM-dd HH:mm:ss.SSS} | 日期时间的格式化字符串。 %d 表示输出日期时间,{ } 中的内容 yyyy-MM-dd HH:mm:ss.SSS 规定了日期时间的显示格式:年-月-日 时:分:秒.毫秒。 |
%c | 日志记录器(Logger)的名称,通常用于标识记录日志的组件或类。 |
%M | 调用日志记录器的方法名,记录日志记录发生的位置,即调用日志记录语句的方法名。 |
%L | 日志记录发生的行号,记录日志语句在源代码中的具体行号。 |
[%thread] | 线程名,记录日志记录发生时的线程名称,通常用于调试多线程程序时查看线程的运行状态。 |
%m | 日志消息,记录需要被输出的具体日志内容。 |
%n | 换行符,用于分隔日志条目,便于阅读和解析。 |
通常情况下咱们就使用默认的日志格式打印即可,运行项目后观察日志变化:
1.9.6 更简单的日志输出
每次都手动使用 LoggerFactory.getLogger(xxx.class) 来创建日志记录器既繁琐,又需要在每个类中重复添加相同的代码。为了解决这个问题,Lombok 提供了一种更简单的方式:
步骤 | 描述 |
---|---|
添加 Lombok 框架支持 | 在项目中引入 Lombok 依赖,Lombok 会自动生成必要的代码,例如日志记录器,从而减少手动编写的重复代码。 |
使用 @Slf4j 注解 | 在类上添加 @Slf4j 注解,Lombok 会自动生成一个名为 log 的日志记录器对象,直接使用 log 对象即可进行日志输出,无需手动创建日志记录器。 |
1.9.6.1 添加 lombok 依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
1.9.6.2 输出日志
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
@Slf4j // 这个注解会在类中自动生成一个 Logger 实例,名称为 log
@RestController
public class LogController {
public void log(){//接着就可以直接使用 log 调用方法
log.info("--------------要输出⽇志的内容----------------");
}
}