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,直接使用即可

相关推荐
吾日三省吾码2 分钟前
JVM 性能调优
java
stm 学习ing7 分钟前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc1 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐1 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi772 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
mqiqe2 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin2 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java