Spring基础知识(26)- Spring Boot (七)


统一日志框架、日志配置及输出

1. 统一日志框架   

    在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。在 Java 领域里存在着多种日志框架,如 JCL、SLF4J、Jboss-logging、jUL、log4j、log4j2、logback 等等。

    1) 日志框架的选择

        日志框架可以被分为两类:日志门面(日志抽象层)和日志实现,如下表。

分类 描述
日志门面(日志抽象层) 为 Java 日志访问提供一套标准和规范的 API 框架,其主要意义在于提供接口。比如:JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-logging
日志实现 日志门面的具体的实现,比如 Log4j、JUL(java.util.logging)、Log4j2、Logback

  
        通常情况下,日志由一个日志门面与一个日志实现组合搭建而成,Spring Boot 选用 SLF4J + Logback 的组合来搭建日志系统。

        SLF4J 是目前最流行的日志门面,使用 Slf4j 可以很灵活的使用占位符进行参数占位,简化代码,拥有更好的可读性。

        Logback 是 Slf4j 的原生实现框架,它与 Log4j 出自一个人之手,但拥有比 log4j 更多的优点、特性和更做强的性能,现在基本都用来代替 log4j 成为主流。

    2) SLF4J 的使用

        在项目开发中,记录日志时不应该直接调用日志实现层的方法,而应该调用日志门面(日志抽象层)的方法。

        在使用 SLF4J 记录日志时,我们需要在应用中导入 SLF4J 及日志实现,并在记录日志时调用 SLF4J 的方法,例如:

 1             import org.slf4j.Logger;
 2             import org.slf4j.LoggerFactory;
 3 
 4             public class App {
 5                 public static void main(String[] args) {
 6                     Logger logger = LoggerFactory.getLogger(HelloWorld.class);
 7                     // 调用 sl4j 的 info() 方法,而非调用 logback 的方法
 8                     logger.info("Hello World");
 9                 }
10             }


        SLF4J 作为一款优秀的日志门面或者日志抽象层,它可以与各种日志实现框架组合使用,以达到记录日志的目的。

        从 SLF4J 官方给出的方案可以看出:

            (1) Logback 作为 Slf4j 的原生实现框架,当应用使用 SLF4J+Logback 的组合记录日志时,只需要引入 SLF4J 和 Logback 的 Jar 包即可;
            (2) Log4j 虽然与 Logback  出自同一个人之手,但是 Log4j 出现要早于 SLF4J,因而 Log4j 没有直接实现 SLF4J,当应用使用 SLF4J+Log4j 的组合记录日志时,不但需要引入 SLF4J 和 Log4j 的 Jar 包,还必须引入它们之间的适配层(Adaptation layer)slf4j-log4j12.jar,该适配层可谓“上有老下有小”,它既要实现 SLF4J 的方法,还有调用 Log4j 的方法,以达到承上启下的作用;
            (3) 当应用使用 SLF4J+JUL 记录日志时,与 SLF4J+Log4j 一样,不但需要引入 SLF4J 和 JUL 的对应的 Jar 包,还要引入适配层 slf4j-jdk14.jar。

        这里我们需要注意一点,每一个日志的实现框架都有自己的配置文件。使用 slf4j 记录日志时,配置文件应该使用日志实现框架(例如 logback、log4j 和 JUL 等等)自己本身的配置文件。

    3) 统一日志框架(通用)

        通常一个完整的应用下会依赖于多种不同的框架,而且它们记录日志使用的日志框架也不尽相同,例如,Spring Boot(slf4j+logback),Spring(commons-logging)、Hibernate(jboss-logging)等等。那么如何统一日志框架的使用呢?

        对此,SLF4J 官方也给出了相应的解决方案。

        统一日志框架一共需要以下 3 步 :

            (1) 排除应用中的原来的日志框架;
            (2) 引入替换包替换被排除的日志框架;
            (3) 导入 SLF4J 实现。

        SLF4J 官方给出的统一日志框架的方案是使用一个替换包来替换原来的日志框架,例如 log4j-over-slf4j 替换 Log4j(Commons Logging API)、jul-to-slf4j.jar 替换 JUL(java.util.logging API)等等。

        替换包内包含被替换的日志框架中的所有类,这样就可以保证应用不会报错,但替换包内部实际使用的是 SLF4J API,以达到统一日主框架的目的。

    4) 统一日志框架(Spring Boot)

        在使用 Spring Boot 时,同样可能用到其他的框架,例如 Mybatis、Spring MVC、 Hibernate 等等,这些框架的底层都有自己的日志框架,此时我们也需要对日志框架进行统一。

        统一日志框架的使用一共分为 3 步,Soring Boot 作为一款优秀的开箱即用的框架,已经为用户完成了其中 2 步:引入替换包和导入 SLF4J 实现。

        SpringBoot 底层使用 slf4j+logback 的方式记录日志,当我们引入了依赖了其他日志框架的第三方框架(例如 Hibernate)时,只需要把这个框架所依赖的日志框架排除,即可实现日志框架的统一,示例代码如下。

 1             
 2                 org.apache.activemq
 3                 activemq-console
 4                 ${activemq.version}
 5                 
 6                     
 7                         commons-logging
 8                         commons-logging
 9                     
10                 
11             

2. 日志配置及输出

    1) 默认配置

        Spring Boot 默认使用 SLF4J+Logback 记录日志,并提供了默认配置,即使我们不进行任何额外配,也可以使用 SLF4J+Logback 进行日志输出。

        常见的日志配置包括日志级别、日志的输入出格式等内容。

        (1) 日志级别

            日志的输出都是分级别的,当一条日志信息的级别大于或等于配置文件的级别时,就对这条日志进行记录。

            常见的日志级别如下(优先级依次升高)。

日志级别 说明
trace 追踪,指明程序运行轨迹。
debug 调试,实际应用中一般将其作为最低级别,而 trace 则很少使用。
info 输出重要的信息,使用较多。
warn 警告,使用较多。
error 错误信息,使用较多。


        (2) 输出格式

            可以通过以下常用日志参数对日志的输出格式进行修改,如下表。

输出格式 说明
%d{yyyy-MM-dd HH:mm:ss, SSS} 日志生产时间,输出到毫秒的时间
%-5level 输出日志级别,-5 表示左对齐并且固定输出 5 个字符,如果不足在右边补 0
%logger 或 %c logger 的名称
%thread  或 %t 输出当前线程名称
%p 日志输出格式
%message 或 %msg 或 %m 日志内容,即 logger.info("message")
%n 换行符
%class 或 %C  输出 Java 类名
%file 或 %F 输出文件名
%L 输出错误行号
%method 或 %M 输出方法名
%l 输出语句所在的行数, 包括类名、方法名、文件名、行数
hostName 本地机器名
hostAddress 本地 ip 地址

 
        示例

 1             package com.example;
 2 
 3             import org.junit.jupiter.api.Test;
 4             import org.slf4j.Logger;
 5             import org.slf4j.LoggerFactory;
 6             import org.springframework.boot.test.context.SpringBootTest;
 7             
 8             @SpringBootTest
 9             class AppTest {
10                 Logger logger = LoggerFactory.getLogger(getClass());
11                 /**
12                  * 测试日志输出
13                  * SLF4J 日志级别从小到大 trace > debug > info > warn > error
14                  */
15                 @Test
16                 void logTest() {
17                     // 日志级别 由低到高
18                     logger.trace("trace 级别日志");
19                     logger.debug("debug 级别日志");
20                     logger.info("info 级别日志");
21                     logger.warn("warn 级别日志");
22                     logger.error("error 级别日志");
23                 }
24             }


            控制台输出:

                2022-04-11 18:11:18.557  INFO 62045 --- [   main] com.example.AppTest  : info 级别日志
                2022-04-11 18:11:18.559  WARN 62045 --- [   main] com.example.AppTest  : warn 级别日志
                2022-04-11 18:11:18.560 ERROR 62045 --- [   main] com.example.AppTest  : error 级别日志                      

    2) 修改默认日志配置

        可以根据自身的需求,通过全局配置文件(application.properties 或 application.yml)修改 Spring Boot 日志级别和显示格式等默认配置。
 
        修改 application.properties,代码如下。

 1             # 日志级别
 2             logging.level.com.example=trace
 3             # 使用相对路径的方式设置日志输出的位置(项目根目录目录\springboot-log\spring.log)
 4             logging.file.path=springboot-log
 5             # 绝对路径方式将日志文件输出到 【项目所在磁盘根目录\springboot-log\spring.log】
 6             # logging.file.path=D:\\temp\\springboot-log
 7             # 控制台日志输出格式
 8             logging.pattern.console=%d{yyyy-MM-dd hh:mm:ss} [%thread] %-5level %logger{50} - %msg%n
 9             # 日志文件输出格式
10             logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === - %msg%n


        运行 AppTest,控制台输出:

            2022-04-11 06:13:52 [main] TRACE com.example.AppTest - trace 级别日志
            2022-04-11 06:13:52 [main] DEBUG com.example.AppTest - debug 级别日志
            2022-04-11 06:13:52 [main] INFO  com.example.AppTest - info 级别日志
            2022-04-11 06:13:52 [main] WARN  com.example.AppTest - warn 级别日志
            2022-04-11 06:13:52 [main] ERROR com.example.AppTest - error 级别日志      

    3) 自定义日志配置

        在 Spring Boot 的配置文件 application.porperties/yml 中,可以对日志的一些默认配置进行修改,但这种方式只能修改个别的日志配置,想要修改更多的配置或者使用更高级的功能,则需要通过日志实现框架自己的配置文件进行配置。

        Spring 官方提供了各个日志实现框架所需的配置文件,用户只要将指定的配置文件放置到项目的类路径下即可。

日志框架 配置文件
Logback logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy
Log4j2 log4j2-spring.xml、log4j2.xml
JUL (Java Util Logging) logging.properties


        从上表可以看出,日志框架的配置文件基本上被分为 2 类:

            (1) 普通日志配置文件,即不带 srping 标识的配置文件,例如 logback.xml;
            (2) 带有 spring 表示的日志配置文件,例如 logback-spring.xml。

        这两种日志配置文件在使用时大不相同,下面我们就对它们分别进行介绍。

        (1) 普通日志配置文件

            将 logback.xml、log4j2.xml 等不带 spring 标识的普通日志配置文件,放在项目的类路径下后,这些配置文件会跳过 Spring Boot,直接被日志框架加载。通过这些配置文件,我们就可以达到自定义日志配置的目的。

            示例

            创建 src/main/resources/logback.xml 文件,内容如下。

 1                 <?xml version="1.0" encoding="UTF-8"?>
 2                 
 9                 "false" scanPeriod="60 seconds" debug="false">
10                     
11                     "LOG_HOME" value="D:\\temp"/>
12                     
13                     "appName" value="springboot-basic-logback">
14 
15                     
16                     "stdout" class="ch.qos.logback.core.ConsoleAppender">
17                         
26                         class="ch.qos.logback.classic.PatternLayout">
27                             %d{yyyy-MM-dd HH:mm:ss} [%thread] ***** %-5level %logger{50} - %msg%n
28                         
29                     
30 
31                     
32                     "appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
33                         
34                         ${LOG_HOME}/${appName}.log
35                         
40                         class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
41                             
45                             ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log
46                             
51                             365
52                             
57                             class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
58                                 100MB
59                             
60                         
61                         
62                         class="ch.qos.logback.classic.PatternLayout">
63                             %d{yyyy-MM-dd HH:mm:ss} [ %thread ] ---- [ %-5level ] [ %logger{50} : %line ] - %msg%n
64                             
65                         
66                     
67 
68                     
76                     
77                     "net.biancheng.www" level="debug"/>
78                     
79                     "org.springframework" level="debug" additivity="false">
80                     
85                     "info">
86                         ref ref="stdout"/>
87                         ref ref="appLogAppender"/>
88                     
89                 


        (2) 带有 spring 标识的日志配置文件

            Spring Boot 推荐用户使用 logback-spring.xml、log4j2-spring.xml 等这种带有 spring 标识的配置文件。这种配置文件被放在项目类路径后,不会直接被日志框架加载,而是由 Spring Boot 对它们进行解析,这样就可以使用 Spring Boot 的高级功能 Profile,实现在不同的环境中使用不同的日志配置。

            示例,创建 src/main/resources/logback-spring.xml 文件,内容如下。

 1                 <?xml version="1.0" encoding="UTF-8"?>
 2                 
 9                 "false" scanPeriod="60 seconds" debug="false">
10                     
11                     "LOG_HOME" value="D:\\temp"/>
12                     
13                     "appName" value="springboot-basic-logback-spring">
14 
15                     
16                     "stdout" class="ch.qos.logback.core.ConsoleAppender">
17                     class="ch.qos.logback.classic.PatternLayout">
18                             
19                             "dev">
20                                 %d{yyyy-MM-dd HH:mm:ss.SSS} ---> [%thread] --> %-5level %logger{50} - %msg%n
21                             
22                             
23                             "!dev">
24                                 %d{yyyy-MM-dd HH:mm:ss.SSS} === [%thread] === %-5level %logger{50} - %msg%n
25                             
26                         
27                     
28 
29                     
30                     "appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
31                         
32                         ${LOG_HOME}/${appName}.log
33                         
38                         class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
39                             
43                             ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log
44                             
49                             365
50                             
55                             class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
56                                 100MB
57                             
58                         
59                         
60                         class="ch.qos.logback.classic.PatternLayout">
61                             %d{yyyy-MM-dd HH:mm:ss} [ %thread ] ---- [ %-5level ] [ %logger{50} : %line ] - %msg%n
62                             
63                         
64                     
65 
66                     
74                     
75                     "net.biancheng.www" level="debug"/>
76                     
77                     "org.springframework" level="debug" additivity="false">
78                     
79                     
84                     "info">
85                         ref ref="stdout"/>
86                         ref ref="appLogAppender"/>
87                     
88