Spring Boot 日志:项目的“行车记录仪”
一、什么是Spring Boot日志
(一)日志引入
在正式介绍日志之前,我们先来看看上篇文章中(Spring Boot 配置文件)中的验证码功能的一个代码片段:
这是一段校验用户输入的验证码是否正确的后端代码, 仔细看,其中有以下三种异常情况,程序会返回false:
(1)用户输入的验证码为空或者没有输入验证码
(2)session为空
(3)验证码超时
那么,当程序运行时,程序返回false时,我们并不知道到底是属于哪一种异常情况,因此,我们借助println方法,打印相关信息,以此来定位返回false返回的是哪种情况,比如,当控制台打印 ” 验证码超时 “ 时,我们就知道,此时返回false是因为验证码已经超时。
通过这种打印信息的方式,可以帮助我们精准定位问题出现的原因,这里打印出来的信息,称为日志!
随着项目复杂度提升,我们对日志打印有更高需求,不仅用于定位排查问题,还需记录用户操作记录(部分审计公司有此要求)、记录用户喜好,将日志持久化后用于数据分析等。但 println 无法很好满足需求,需使用专门日志框架。
当我们启动项目时,是不是经常会看到如下图的信息,其实这就是日志框架生成的日志!
(二)日志与行车记录仪
我们可以将日志类比为生活中的行车记录仪,它们在功能和作用上有很多相似之处:
记录功能:行车记录仪会持续记录车辆行驶过程中的各种信息,比如行驶路线、速度、时间以及周围的路况等,就像日志会记录 Spring Boot 应用程序运行过程中的各种信息,包括方法调用、参数传递、执行时间和发生的事件等。
问题排查:当车辆发生事故或者出现一些异常情况时,车主可以通过查看行车记录仪的记录来还原当时的场景,分析事故发生的原因或者车辆出现问题的可能因素。类似地,当 Spring Boot 应用出现故障或错误时,开发人员和运维人员可以通过查看日志来了解应用在故障发生前后的运行情况,快速定位问题所在,比如是哪个方法出现了异常,在什么时间、什么条件下出现的等。
行为追溯:如果遇到一些纠纷,例如在判断是否发生追尾等事故责任时,行车记录仪的记录可以作为重要的证据,清晰地呈现车辆的行驶状态和相对位置等情况,帮助确定责任归属。在 Spring Boot 应用中,日志也可以用于追溯用户的操作行为和系统的响应过程,对于一些需要审计或者追踪的场景非常有用,比如记录用户的登录登出时间、对重要数据的修改操作等,以便在需要时进行查询和核对。
性能评估:通过行车记录仪记录的行驶数据,比如在不同路段的行驶速度、停车次数等,还可以分析车辆的行驶性能和效率,判断是否存在行驶路线不合理或者车辆本身性能问题等。同样,通过分析 Spring Boot 应用的日志,能够评估应用的性能状况,例如某个接口的响应时间是否过长,数据库查询的频率是否过高,从而为性能优化提供依据。
(三)简单打印一个日志
接下来让我们学习一下如何使用日志框架来打印一个日志!
由于程序是在main方法中运行的,因此线程名称是main;此外,在获取日志对象的时候,我们填入LoggerController.class作为参数,因此日志的来源类就是它,这样做可以让我们更加精准地定位日志来源于哪个类!
二、门面模式
(一)什么是门面模式
在很久之前,市场上存在多种日志框架,如 Log4j、Logback、java.util.logging 等,它们的使用方式各不相同,比如日志框架1是使用log1()方法来打印日志的,日志框架2是使用log2()方法来打印日志的。
那么在一个大型项目中,可能会有多个团队或开发者参与开发,如果没有统一的日志记录规范,不同团队可能会使用不同的日志框架,会导致代码风格和日志记录方式混乱。
那么,该如何解决这个问题呢?
虽然各大框架的使用方式不同,但是,我们是不是可以使用一个平台,让这个平台集成各种不同的日志框架,然后将不同框架中功能相同的方法,对外提供一个统一的接口,供程序员使用,这样就解决了代码风格不一致的问题。这种模式就叫 " 门面模式 "(这里的平台就被称为——日志门面)。
上面说的 slf4j 其实就是日志门面,它为所有开发者提供了一套统一的日志记录接口,无论底层使用哪种日志框架,开发者都使用相同的方式记录日志,有助于保持代码的一致性和规范性,降低代码的学习和维护成本。
门面模式(Facade Pattern)又称为外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用。
(二)门面模式的实现
下面我们来实现一个简单的门面模式,我们想象这样一个场景,当我们回家时,我们可能会打开客厅的灯,打开卧室的灯、打开走廊的灯……
如果没有使用门面模式,我们需要一个一个走过去相应的场所去打开灯,那么,如果使用门面模式,我们就可以通过一个总开关,一次性打开所有地方的灯……
1、我们先定义客厅、卧室、走廊三个类,类中都有一个on()方法,用于打开灯
客厅类:
卧室类:
走廊类:
2、传统模式下开灯:
传统模式下开灯,我们需要在main方法中创建客厅、卧室、走廊三个类的实例,然后调用其 on()方法开灯,特别麻烦。
3、门面模式下开灯:
首先,我们定义一个门面,里面集成了客厅、卧室、走廊。
现在我们开灯的时候,就只需要实例Facade对象的实例,然后调用其on()方法即可开灯,比之前方便不少!
在这个案例中,我们可以把客厅、卧室、走廊类比为不同的日志框架,我们发现,即便是框架里面的方法名称改变了,比如客厅打开灯的方法名不是on(),而变成了livingOn()了,此时用户也无需去操心这种方法名变化对自己开发有什么影响,因为门面对外提供的接口还是on(),框架改变产生的影响,只需要门面去考虑,只要门面对外提供的接口不变,用户就无需去操心框架的改变带来的影响。
三、日志级别
(一)日志格式说明
在前面打印日志的例子中,我简单介绍了一下日志的格式,接下来,我再正式的详细介绍一下日志格式,带大家深入了解一下,日志内容究竟包含了什么信息?
这段日志是典型的 Spring Boot 应用程序启动日志,以下是对其格式的说明:
时间戳:日志的开头是时间戳,例如 2025-01-28T10:11:09.772+08:00,表示日志记录的时间,精确到毫秒,并且包含了时区信息(+08:00 表示东八区)。
日志级别:紧接着时间戳的是日志级别,如 INFO,表示日志的重要程度。常见的日志级别还有 DEBUG、WARN、ERROR 等。
进程 ID:9852 是进程 ID,标识生成该日志的进程。
线程名称:[spring_setting_test3] 是线程名称,表明该日志是由哪个线程产生的。通常是源代码的类名。
日志记录器名称:中括号后面的内容,如 main 是日志记录器名称,用于标识日志的来源。
日志内容:冒号后面的部分是具体的日志信息,描述了应用程序在启动过程中发生的事件,
(二)日志级别
上面在介绍日志格式的时候,我们看到了“ 日志格式 “,那么,啥是日志级别呢?
日志级别:是在软件开发和系统运维中用于对日志信息进行分类和分级的一种标准机制,大白话来说就是:日志级别就是对日志消息轻重程度进行了分级。
就好像天气预报根据降雨的严重情况,对雨进行分级:小雨、大雨、暴雨、雷阵雨……同样,日志消息也有轻重缓急之分,有的消息只会记录一些小事,有些日志消息会记录一些程序奔溃的错误消息。
日志的级别从高到低依次为:FATAL、ERROR、WARN、INFO、DEBUG、TRACE
FATAL:致命信息,表示需要立即被处理的系统级错误。
ERROR:错误信息,级别较高的错误日志信息,但仍然不影响系统的继续运行。
WARN:警告信息,不影响使用,但需要注意的问题。
INFO:普通信息,用于记录应用程序正常运行时的一些信息,例如系统启动完成、请求处理完成等。
DEBUG:调试信息,需要调试时候的关键信息打印。
TRACE:追踪信息,比 DEBUG 更细粒度的信息事件 (除非有特殊用意,否则请使用 DEBUG 级别替代)
(三)日志级别配置
默认情况下,日志框架只会打印 info级别 或 info 级别以上级别的日志消息,因为如果打印info级别以下的日志消息记录的是一些繁琐的小事,因此默认不打印出来。
观察上面的main方法,我们打印了除了FATAL级别的日志消息(日志框架没有提供FATAL级别的日志打印),但是运行程序时,只打印 INFO 级别或 INFO 级别以上的日志消息。
其实,这是日志配置的默认输出级别是 INFO ,程序只会默认输入这个级别或以上级别的日志,那么,既然是默认的,我们也可以自定义设置默认级别为其他级别。
1、在yml文件中配置日志输出级别为DEBUG:
运行main方法:
我们发现,虽然设置默认输出级别为debug,但是运行mian方法时,却没有打印出来debug级别的日志,这是为什么呢?
这是因为,你手动运行main方法,配置文件对main方法是不起作用的,我们得通过url访问调用main方法,这样配置文件才会起作用。
代码修改:
Postman访问调用main方法:
2、接下来我们在yml文件中配置日志输出级别为TRACE:
Postman访问调用main方法:
补充:因为FATAL级别的消息是致命消息,一旦出现,系统就无法正常运行。只要用户看到系统崩溃,就知道系统已经出现了致命错误,所有无需打印出来告知用户,因此日志框架没有提供FATAL级别的日志打印。
四、日志(补充)
(一)日志持久化
我们发现,一旦程序结束运行,我们之前的日志消息就全部被清空了。那么我们能不能生成一个日志文件,让它来记录日志信息呢?
有的,兄弟,有的,像生成日志文件这样的的方法我们有两种,并且都是通过配置文件配置即可自动生成!
配置方法1:yml 配置日志文件名:
#配置日志文件名存储历史日志
logging:
file:
name: logger/springBoot.log
配置后启动项目,日志框架会生成 logger/springBoot.log 日志文件
配置方法2:yml配置日志文件路径:
#配置日志文件路径:会在D:/temp 路径下生成日志文件
logging:
file:
path: D:/temp
配置后启动项目,日志框架会在D:/temp路径下自动生成日志文件
(二)更简单的日志输出
前面教学的打印日志,我们需要创建一个日志对象,才可以打印日志,那么有没有什么更好的方法来打印日志呢?也是有的,兄弟!我们可以使用 @Slf4j 注解
旧方法:
新方法代码: 无需创建日志对象啦!直接使用!
Postman访问调用print方法:
背后原理:
@Slf4j注解 的作用是自动生成日志对象。在使用 @Slf4j 注解的类中,它相当于帮开发者自动添加了private final Logger log = LoggerFactory.getLogger(当前类名.class); 这行代码。 通过LoggerFactory.getLogger(当前类名.class)语句,以当前类作为参数初始化日志记录器,这样在打印日志时,就能明确日志信息出自哪个类。