Spring Boot 日志

1. 日志概述

  1. 早期使用:从 JavaSE 部分就开始用 System.out.print 打印日志来发现和定位问题,在 Spring 学习中也常根据控制台日志分析定位问题。
  2. 更高需求:随着项目复杂度提升,对日志打印有了更高需求,如记录用户操作、喜好并持久化用于数据分析等,而 System.out.print 不能满足,需使用专门日志框架。

日志用途:

  1. 系统监控

    • 监控是成熟系统标配,可通过日志记录系统运行状态、方法响应时间和状态等。
    • 能统计日志中关键字数量,达到一定条件报警。
  2. 数据采集

    • 采集数据范围大,可用于数据统计(如统计页面浏览量、访客量、点击量等进行数据分析优化运营策略)和推荐排序(记录用户浏览历史、停留时长等供算法人员训练模型做推荐)。
    • 数据源一部分来自日志记录的数据。
  3. 日志审计

    • 网络安全受关注,系统安全重要,日志审计是关键部分,国家政策法规和行业标准有明确要求。
    • 通过系统日志分析可判断非法攻击、调用及安全隐患。
    • 如运营系统中,有日志操作记录可明确数据被谁删除或修改;内部违规和信息泄漏若有日志留存可为事后调查提供依据。

2. 日志使用

2.1 打印日志

打印日志的步骤:

  • 在程序中得到日志对象
  • 使用日志对象输出要打印的内容

2.1.1 在程序中得到日志对象

在程序中获取日志对象需要使用日志工厂 LoggerFactory,如下代码所示:

java 复制代码
private static Logger logger = LoggerFactory.getLogger(CaptchaController.class);

LoggerFactory.getLogger 需要传递一个参数,标识这个日志的名称,这样可以更清晰的知道是哪个类输出的日志,当有问题时,可以更方便直观的定位到问题类

tip:Logger 对象属于 org.slf4j 包下的!!!

2.1.2 使用日志对象打印日志

2.2 日志框架介绍

SLF4J 不同于其他日志框架,它不是一个真正的日志实现,而是一个抽象层,对日志框架指定的一种规范、标准、接口,所以 SLF4J 并不能独立使用,需要和具体的日志框架配合使用

2.2.1 门面模式(外观模式)

SLF4J 是门面模式的典型应用(但不仅仅使用了门面模式)

门面模式定义

门面模式(Facade Pattern)又称为 外观模式,提供了一个统一的接口,用来访问子系统中的一群接口,其主要特征是定义了一个高层接口,让子系统更容易使用

门面模式主要包含 2 种角色:

外观角色(Facade):也称门面角色,系统对外的统一接口

子系统角色(SubSystem):可以同时有一个或多个 SubSystem,每个 SubSystem 都不是一个单独的类,而是一个类的集合。SubSystem 并不知道 Facade 的存在,对于 SubSystem 而言,Facade 只是另一个客户端而已(即 Facade 对 SubSystem 透明)

门面模式的实现

场景:晚上回家,我们会打开各个屋的灯,离开家时,会关闭各个屋的灯

java 复制代码
// 灯的接口
public interface Light {
    void on();
    void off();
}

// 走廊灯
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HallLight implements Light{
    private static Logger logger = LoggerFactory.getLogger(HallLight.class);

    @Override
    public void on() {
        logger.info("打开走廊灯");
    }

    @Override
    public void off() {
        logger.info("关闭走廊灯");
    }
}

// 客厅灯
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LivingRoomLight implements Light{
    private static Logger logger = LoggerFactory.getLogger(LivingRoomLight.class);

    @Override
    public void on() {
        logger.info("打开客厅灯");
    }

    @Override
    public void off() {
        logger.info("关闭客厅灯");
    }
}

// 卧室灯
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BedRoomLight implements Light{
    private static Logger logger = LoggerFactory.getLogger(BedRoomLight.class);

    @Override
    public void on() {
        logger.info("打开卧室灯");
    }

    @Override
    public void off() {
        logger.info("关闭卧室灯");
    }
}

没有总开关的情况:

java 复制代码
public class Main {
    public static void main(String[] args) {
        HallLight hallLight = new HallLight();
        LivingRoomLight livingRoomLight = new LivingRoomLight();
        BedRoomLight bedRoomLight = new BedRoomLight();

        // 把灯全部打开
        hallLight.on();
        livingRoomLight.on();
        bedRoomLight.on();
    }
}

如果家里设置一个总开关,来控制整个屋的灯就很方便

使用门面模式实现:

java 复制代码
public class LightFacade {
    void lightOn() {
        HallLight hallLight = new HallLight();
        LivingRoomLight livingRoomLight = new LivingRoomLight();
        BedRoomLight bedRoomLight = new BedRoomLight();

        hallLight.on();
        livingRoomLight.on();
        bedRoomLight.on();
    }

    void lightOff() {
        HallLight hallLight = new HallLight();
        LivingRoomLight livingRoomLight = new LivingRoomLight();
        BedRoomLight bedRoomLight = new BedRoomLight();

        hallLight.off();
        livingRoomLight.off();
        bedRoomLight.off();
    }
}

public class Main {
    public static void main(String[] args) {
        LightFacade lightFacade = new LightFacade();
        lightFacade.lightOn(); // 开灯
        lightFacade.lightOff(); // 关灯
    }
}

运行结果:

门面模式的优点

减少了系统的相互依赖,降低了了客户端与子系统的耦合关系,这使得子系统的变化不会影响到调用它的客户端

提高了灵活性,简化了客户端对子系统的使用难度,客户端无需关心子系统的具体实现方式,而只需要和门面对象交互即可

提高了安全性,可以灵活设定访问权限,不在门面对象种开通方法,就无法访问

2.2.2 SLF4J 框架介绍

SLF4J 就是其他日志框架的门面,SLF4J 可以理解为是提供日志服务的统一 API 接口,并不涉及到具体的日志逻辑实现

若是不引入日志门面:

常见的日志框架有 log4j、logback 等,如果一个项目已经使用了 log4j,而此时另一个依赖的库却依赖于另一个日志框架 logback,那就需要把 logback 也加载进去

这样做存在的问题:

  1. 不同日志框架的 API 接口和配置文件不同,如果多个日志框架共存,那么不得不维护多套配置文件(这个配置文件是指用户自定义的配置文件)。
  2. 如果要更换日志框架,应用程序将不得不修改代码,并且修改过程中可能会存在一些代码冲突。
  3. 如果引入的第三方框架,使用了多套,那就不得不维护多套配置。

引入日志门面:

引入日志门面之后,应用程序和日志框架(框架的具体实现)之间有了统一的 API 接口(门面日志框架实习),此时应用程序只需要维护一套日志文件配置,且当底层实现框架改变时,也不需要更改应用程序代码

SLF4J 就是这个日志门面

2.3 日志级别

日志级别代表着日志信息对应问题的严重性,为了更快的筛选符合目标的日志信息

2.3.1 日志级别的分类

目志的级别从高到低依次为:FATAL、ERROR、WARN、INFO、DEBUG、TRACE。

  • FATAL: 致命信息: 表示需要立即被处理的系统级错误。
  • ERROR: 错误信息, 级别较高的错误日志信息, 但仍然不影响系统的继续运行。
  • WARN: 警告信息, 不影响使用, 但需要注意的问题。
  • INFO: 普通信息, 用于记录应用程序正常运行时的一些信息, 例如系统启动完成、请求处理完成等。
  • DEBUG: 调试信息, 需要调试时候的关键信息打印。
  • TRACE: 追踪信息, 比 DEBUG 更细粒度的信息事件(除非有特殊用意: 否则请使用 DEBUG 级别替代)。

日志级别通常和测试人员的 Bug 级别没有关系。

日志级别是开发人员设置的,用来给开发人员看的。日志级别的正确设置,也与开发人员的工作经验有关。如果开发人员把 error 级别的日志设置成了 info,就很有可能会影响开发人员对项目运行情况的判断。出现 error 级别的日志信息较多时,可能也没有任何问题。测试的 bug 级别更多是依据现象和影响范围来判断。

日志级别的顺序

级别越高,收到的消息越少

2.3.2 日志级别的使用

日志级别是开发人员自己设置的,开发人员根据自己的理解来判断该信息的重要程度

针对这些级别,Logger 对象分别提供了对应的方法来输出日志:

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/logger")
@RestController
public class LoggerLevelController {
    private static Logger logger = LoggerFactory.getLogger(LoggerLevelController.class);

    @RequestMapping("/print")
    public String print() {
        logger.trace("trace 级别日志...");
        logger.debug("debug 级别日志...");
        logger.info("info 级别日志...");
        logger.warn("warn 级别日志...");
        logger.error("error 级别日志...");
        return "打印日志";
    }
}

SpringBoot 默认的日志框架是 Logback,Logback 没有 FATAL 级别,它被映射到 ERROR。

出现 fatal 日志,表示服务已经出现了某种程度的不可用,需要系统管理员紧急介入处理。通常情况下,一个进程生命周期中应该最多只有一次 FATAL 记录。

观察打印日志结果:

发现只打印了 info、warn、error 级别的日志

这与日志级别的配置有关,日志的输出级别默认是 info 级别,所以只会打印大于等于此级别的日志,也就是 info、warn、error

2.4 日志配置

2.4.1 配置日志级别

日志级别配置只需要在配置文件种设置 logging.level 配置项即可,如下所示:(properties 和 yml 只需配置其中一个即可)

properties 配置:

java 复制代码
logging.level.root=trace

yml 配置:

java 复制代码
logging:
  level:
    root: trace

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

还可以分目录设置日志级别,如:只想让 com.example.captchademo.controller 该路径下的项目的日志级别为 trace

html 复制代码
logging:
  level:
    root: info
    com:
      example:
        captchademo:
          controller: trace

运行结果:

2.4.2 日志持久化

以上的日志都是输出在控制台上的,然而在线上环境中,我们需要把日志保存下来,以便出现问题后追溯问题,把日志保存下来就叫持久化

日志持久化有两种方式

  1. 配置日志文件名
  2. 配置日志的存储目录

配置日志文件的路径和文件名:

properties 配置

html 复制代码
logging.file.name=logger/springboot.log

yml 配置

html 复制代码
logging:
  file:
    name: logger/springboot.log

后面可以跟绝对路径或者相对路径

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

配置日志文件的保存路径

properties 配置

html 复制代码
logging.file.path=D:/temp

yml 配置

html 复制代码
logging:
  file:
    path: D:/temp

这种方式只能设置日志的路径,文件名为固定的 spring.log

运行程序,该路径下多出一个日志文件:spring.log

tip:logging.file.name 和 logging.file.path 两个都配置的情况下,只生效其一,以 logging.file.name 为准

2.4.3 配置日志文件分割

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

日志框架帮我们考虑到了这一点,如果不进行配置,就会执行默认配置(日志文件超过 10M 就进行分割)

Spring Boot 常见的 Application Properties

中文版

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|----------------------------------|
| 配置项 | 说明 | 默认值 |
| logging.logback.rollingpolicy.clean-history-on-start | 是否在启动时清理存档日志文件 | false |
| logging.logback.rollingpolicy.file-name-pattern | 日志分割后的文件名格式 | ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
| logging.logback.rollingpolicy.max-file-size | 日志文件超过这个大小就自动分割 | 10MB |

配置文件分割

properties 配置

html 复制代码
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB

yml 配置

html 复制代码
logging:
  logback:
    rollingpolicy:
      max-file-size: 1KB
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i

日志文件超过 1KB 就分割(设置 1KB 是为了更好展示,企业开发中通常设置为 200M、500M等,此处没有明确标准)

分割后的日志文件名为:日志名.日期.索引

项目运行,多打印一些日志,日志分割结果:

tip:可以看到很多文件都是大于 1KB 的,这是因为日志是按照行来存储的,就算一行日志有 5KB 大小,它也是存完这一行之后再进行分割

2.4.4 配置日志格式

目前日志打印的格式是默认的

打印日志的格式也是支持配置的,支持控制台和日志文件分别设置

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 配置项 | 说明 | 默认值 |
| 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} |
| 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} |

配置项说明:

  1. %clr(表达式)颜色}设置输入日志的颜色,支持颜色有以下几种:

blue、cyan、faint、green、magenta、red、yellow。

  1. %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,若属性 LOG_DATEFORMAT_PATTERN 不存在,则使用 -yyyy-MM-dd HH:mm:ss.SSSXXX 格式,系统属性可以 System.getProperty("LOG_DATEFORMAT_PATTERN") 获取。

  1. %5p 显示日志级别 ERROR、WARN、INFO、DEBUG、TRACE。

  2. %t 线程名,%c 类的全限定名,%M method,%L 为行号,%thread 线程名称,%m 或者 %msg 显示输出消息,%n 换行符。

  3. %5 若字符长度小于 5,则右边用空格填充。%-5 若字符长度小于 5,则左边用空格填充。%.15 若字符长度超过 15,截去多余字符。%15.15 若字符长度小于 15,则右边用空格填充。若字符长度超过 15,截去多余字符。

更多说明参考:Chapter 6: Layouts

设置了颜色却没生效?

需要配置让 idea 支持控制台颜色显示

properties 配置

html 复制代码
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'

yml 配置

html 复制代码
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'

运行结果:

3. 更简单的日志输出

每次都使用 LoggerFactory.getLogger(xxx.class) 很繁琐,且需将每个类都添加一遍,lombok 给我们提供了一种更简单的方式

3.1 添加 lombok 依赖

html 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

3.2 输出日志

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/logger2")
@RestController
public class LoggerLevelController2 {

    @RequestMapping("/print")
    public String print() {
        log.trace("trace 级别日志...");
        log.debug("debug 级别日志...");
        log.info("info 级别日志...");
        log.warn("warn 级别日志...");
        log.error("error 级别日志...");
        return "打印日志";
    }
}

lombok 提供的 @Slf4j 会帮我们提供一个日志对象 log,直接使用即可

相关推荐
海威的技术博客22 分钟前
JS中的原型与原型链
开发语言·javascript·原型模式
WPG大大通29 分钟前
基于DIODES AP43781+PI3USB31531+PI3DPX1207C的USB-C PD& Video 之全功能显示器连接端口方案
c语言·开发语言·计算机外设·开发板·电源·大大通
蓝天星空36 分钟前
spring cloud gateway 3
java·spring cloud
罗政40 分钟前
PDF书籍《手写调用链监控APM系统-Java版》第9章 插件与链路的结合:Mysql插件实现
java·mysql·pdf
从以前43 分钟前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
一根稻草君1 小时前
利用poi写一个工具类导出逐级合并的单元格的Excel(通用)
java·excel
kirito学长-Java1 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
木头没有瓜1 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
奋斗的老史1 小时前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring