Spring - 7 ( 10000 字 Spring 入门级教程 )

一: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” 配置项即可,如下所示:

  1. Properties 配置
logging.level.root: debug
  1. yml配置
logging:
	level:
		root: debug

重新运行上述代码, 观察结果:

在这里插入图片描述

1.9.2 日志持久化

以上日志默认是输出到控制台的,但在生产环境中,我们通常需要将日志保存下来,以便在出现问题时能够进行追踪和分析。这种将日志长期保存的过程称为日志持久化。日志持久化主要有以下两种方式:

持久化方式描述
配置日志文件名通过配置日志的文件名,将日志信息保存到指定的文件中。
配置日志存储目录通过设置日志的存储路径,将日志文件保存到特定的目录中,便于管理和查找。

在这里插入图片描述

1.9.2.1 配置日志文件名
  1. Properties 配置
#日志会保存在这个文件中
logging.file.name: logger/springboot.log
  1. yml 配置
# 设置⽇志⽂件的⽂件名
logging:
	file:
		name: logger/springboot.log

运行结果后日志内容保存在了对应的目录下:

在这里插入图片描述

1.9.2.2 配置日志的存储目录
  1. Properties 配置
logging.file.path: D:/temp
  1. yml 配置
# 设置⽇志⽂件的⽬录
logging:
	file:
		path: D:/temp

运行程序后该路径下多出⼀个日志⽂件: spring.log

在这里插入图片描述
注意:当同时配置 logging.file.name 和 logging.file.path 时,只有 logging.file.name 会生效,logging.file.path 将被忽略。

1.9.3 配置日志文件分割

如果我们的日志都放在⼀个文件中,随着项目的运行日志文件会越来越大,所以我们需要对日志文件进行分割。

  1. Properties 配置
#这行代码用于配置日志分割后的文件名格式,${LOG_FILE} 是一个占位符,表示日志文件的名称
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
#用于配置一个日志文件的最大大小
logging.logback.rollingpolicy.max-file-size=2KB
  1. 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 修改日志的默认格式

下面举一个简单的代码例子来修改日志的默认格式

  1. Properties 配置
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
  1. 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("--------------要输出⽇志的内容----------------");
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ice___Cpu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值