Logback的使用

1、基本认识

logback官方文档:http://logback.qos.ch

具体样例:https://www.baeldung.com/logback

从下面依赖关系图可以看见,Springboot的核心启动器spring-boot-stater依赖了spring-boot-starter-looging,而这个就是日志的启动器。

Springboot项目整合了日志框架,它默认使用Slf4j作为日志门面,默认的日志实现使用Logback。用JAVA来理解的话,Slf4j就是接口,而logback就是它的具体实现类。

如果你使用的日志实现不是logback。比如,使用的日志实现是log4j2,在Springboot启动的时候,也会自动通过桥接包《log4j-to-slf4j》,将log4j2切换到slf4j的门面,然后使用logback输出日志到控制台。

2、实际演示

pom文件中引入SpringBoot的Web启动依赖

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.7.RELEASE</version>
</dependency>

需要注意,需要导入的Logger类是在【org.slf4j】下,不要错误。

①日志工具的logger对象应当用private static final修饰,private保证不会被其他类调用,static final保证在类加载时就被初始化,并且其值不会改变,减少内存消耗。

②打印日志时,尽量不要使用"+"进行拼接,这会额外消耗内存,尽量使用{}占位符。

java 复制代码
package com.travel.logback.study.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

@RestController
public class DemoController {

    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
    
    //该注解的作用是让Springboot启动时,自动执行该方法
    @PostConstruct
    public void testLogback(){
        logger.trace("---trace 级别的日志 ---");
        logger.debug("---debug 级别的日志 ---");
        logger.info("---info 级别的日志 ---");
        logger.warn("---warn 级别的日志 ---");
        logger.error("---error 级别的日志 ---");
    }
}

启动项目,控制台打印效果如下:

可以发现一个问题,trace级别 和 debug级别的日志并没有出现在控制台。这是因为Springboot启动时,控制台打印的日志级别默认是info级别,低于info级别的日志,就不会打印在控制台上。如果需要打印低级别的日志,就需要修改配置文件内容,指定控制台打印的日志级别。

logback的日志级别

从低到高:TRACE < DEBUG < INFO < WARN < ERROR < FATAL

可以在yml文件中设置日志级别

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

3、配置文件的学习

由于springboot默认使用logback作为日志实现,因此可以在resources目录下直接创建一个【logback.xml】配置文件进行一些个性化的配置。

3.1 完整配置文件内容 和 展示效果

完整配置文件

XML 复制代码
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!--    property指定日志输出格式,他有两个属性:
                    name定义property节点的名称,value属性设置具体的日志输出格式
            property节点定义之后,下面的节点可以直接使用"${}"来引用value中定义的日志输出格式
     -->
    <!--
        日志输出格式:
        %-5level                      %level表示日志级别,-5表示占5个字符,如果不足,就向左对齐
        %d{yyyy-mm-dd H:mm:ss.sss}    %d表示日期,后面是日期的格式
        %c                            表示  类的完整名称
        %M                            表示  method
        %L                            表示  行号
        %thread                       表示  线程名称
        %m或者%msg                     表示  信息
        %X{key}                  %X表示输出MDC中特定键的值,key为具体的键名称,值不存在,则不会输出
        %logger{36}                   表示 使用哪个日志记录器,就会打印那个日志记录器的name,最多显示36个字符
        %n                            表示   换行
        被[]中括号括起来,只是为了方便区分,也可以将中括号去掉,不会有影响
    -->
    <!--%X{key}   %X表示输出MDC中特定键的值,key为具体的键名称,值不存在,则不会输出-->
    <property name="NEW_LOG_STYLE"
              value="[%-5level] [%thread] [%logger] [%X{traceId-UMR}] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n"/>

    <!--定义日志文件的保存路径-->
    <property name="BASE_LOG_HOME" value="/opt/applog"/>
    <property name="MAX_FILE_SIZE" value="1MB"/>
    <property name="MAX_HISTORY" value="3"/>
    <!--项目的名称-->
    <property name="SERVICE_NAME" value="ACCOUNT_MANAGE_SYSTEM"/>

    <!-- 控制台输出设置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--encoder指定日志格式,class属性可以不写,默认会将值映射到PatternLayoutEncoder的变量中-->
        <!-- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> -->
        <encoder>
            <!--使用上文定义的,全局的property配置-->
            <pattern>${NEW_LOG_STYLE}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--定义一个日志文件输出的appender,RollingFileAppender 可以实现日志拆分、和归档压缩 -->
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--file标签指定日志文件保存路径-->
        <file>${BASE_LOG_HOME}/${SERVICE_NAME}_rollback.log</file>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件的最大限制 -->
            <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            <!-- 日志文件保留时间,默认以天为单位 -->
            <maxHistory>${MAX_HISTORY}</maxHistory>
            <!-- 日志每超过一次最大限制,则按时间压缩一次,%i是一个计数,从0开始,往上递增-->
            <fileNamePattern>${BASE_LOG_HOME}/${SERVICE_NAME}/%d{yyyy-MM-dd}.log%i.log.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <!--指定log文件中的日志格式-->
            <pattern>${NEW_LOG_STYLE}</pattern>
        </encoder>
    </appender>

    <!--日志记录器,additivity默认也是true,如果为true,则使用父节点的appender进行输出-->
    <logger name="com.travel.logback.study.web" level="INFO" additivity="true" />

    <logger name="Wechat-UMR" level="debug" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <!--调整某一个类的日志输出级别,name写入类的全限定名称,level设置日志级别-->
    <logger name="org.hibernate.XXXX" level="ERROR" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <root level="info">
        <!--匹配到当前日志记录器之后,会执行下面两个appender标签-->
        <appender-ref ref="LOG_FILE"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

3.2 各节点介绍

<configuration>根节点

包含的属性如下所示:

scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。

scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。

debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

<property> 标签

复制代码
property定义全局的变量,他有两个属性:
        name属性定义property节点的名称,
        value属性设置具体的日志输出格式
property标签定义之后,能够方便其他标签通过“${name}”来获取value中的值


value可配置的日志输出格式:
%-5level                      %level表示日志级别,-5表示占5个字符,如果不足,就向左对齐
%d{yyyy-mm-dd H:mm:ss.sss}    %d表示日期,后面是日期的格式
%c                            表示  类的完整名称
%M                            表示  method
%L                            表示  行号
%thread                       表示  线程名称
%m或者%msg                    表示  信息
%X{key}                  %X表示输出MDC中特定键的值,key为具体的键名称,值不存在,则不会输出
%logger{36}          表示使用哪个日志记录器,就会打印那个日志记录器的name,最多显示36个字符
%n                            表示  换行
XML 复制代码
<property name="UMR_LOG_STYLE" value="[%-5level] [%thread] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n"/>

用 " 中括号[ ] " 括起来,只是为了方便查看,可以将中括号去掉,不会有任何影响

<appender>标签

appender用来指定日志输出方式,有两个属性name和class。由class决定使用哪种输出策略,常用就是控制台输出策略文件输出策略ConsoleAppender 是将日志输出到控制台。FileAppender 是将日志输出到一个文件中。

使用控制台输出策略
XML 复制代码
<property name="UMR_LOG_STYLE" value="[%-5level] [%thread] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n"/>

<!-- 控制台输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
   <!--encoder标签指定日志的格式-->
    <encoder>
       <!--引用指定property配置,获取它的value值-->
       <pattern>${UMR_LOG_STYLE}</pattern>
       <charset>UTF-8</charset>
   </encoder>
</appender>

<!--日志记录器,使用类的全限定名称进行匹配,表示只捕获这一个类的日志-->
<logger name ="com.travel.logback.study.web.DemoController" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
</logger>
使用文件输出策略

将日志输出到文件比较麻烦,需要考虑输出到哪里,一个log文件最大是多少,保存多少天,文件超过最大又该如何处理。

针对这些问题,可以使用RollingFileAppender,它里面的属性能够帮助我们控制文件大小、根据时间将日志信息进行压缩等等。

ch.qos.logback.core.rolling.RollingFileAppender这个类有一个rollingPolicy成员变量

而RollingPolicy只是一个接口,所以我们使用SizeAndTimeBasedRollingPolicy这个实现类,这个实现类的源码如下:

类图如下

观察类图可以发现TimeBasedRollingPolicy继承于RollingPolicyBase。RollingPolicyBase有个fileNamePattern变量,能够帮助我们按照某种规则拆分日志。

XML 复制代码
    <!--项目存放路径-->
    <property name="BASE_LOG_HOME" value="/opt/applog" />
    <property name="MAX_FILE_SIZE" value="1MB" />
    <property name="MAX_HISTORY" value="3" />
    <!--项目的名称-->
    <property name="SERVICE_NAME" value="ACCOUNT_MANAGE_SYSTEM" />

    <property name="NEW_LOG_STYLE"
              value="[%-5level] [%thread] [%logger] [%X{traceId-UMR}] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n"/>


<!-- 定义一个日志文件输出的appender,RollingFileAppender 可以实现日志拆分、和归档压缩 -->
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--
            会根据给定的路径创建对应文件夹以及log文件
            【/opt/applog】会从根路径创建文件夹。如果是windows系统,会默认从当前项目所在磁盘的根路径开始,如果处于D盘,则会创建 D:/opt/applog目录

            【opt/applog】是在当前项目下创建一个opt/applog目录
        -->
        <!--file标签指定日志文件保存路径-->
        <file>${BASE_LOG_HOME}/${SERVICE_NAME}_rollback.log</file>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件的最大限制 -->
            <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            <!-- 日志文件保留时间,默认以天为单位 -->
            <maxHistory>${MAX_HISTORY}</maxHistory>
            <!-- 日志文件每超过一次最大限制,则按时间压缩一次,%i是一个计数,从0开始,往上递增-->
            <fileNamePattern>${BASE_LOG_HOME}/${SERVICE_NAME}/%d{yyyy-MM-dd}.log%i.log.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <!--指定log文件中的日志格式-->
            <pattern>${NEW_LOG_STYLE}</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="LOG_FILE" />
        <appender-ref ref="CONSOLE" />
    </root>

我在logback文件中写的是超过1M就进行压缩,通过for循环模拟1w次请求。(生产环境,日志文件肯定不要限制这么小,这里只是为了方便测试)

java 复制代码
@RestController
public class DemoController {

    private Logger logger = LoggerFactory.getLogger(DemoController.class);

    @PostConstruct
    public void testLogback() {
        for (int i = 0; i < 10000; i++) {
            logger.info("---info 级别的日志 ---");
            logger.warn("---warn 级别的日志 ---");
            logger.error("---error 级别的日志 ---");
        }

    }
}

最外层的log,因为我们限制文件大小为1MB,所以肯定不会超过

压缩包是经过压缩之后形成的,肯定没有1M,但解压之后,他的文件大小就是1M左右了

为什么有些appender节点下面存在<file>、<rollingPolicy>等子节点,有些又不存在?

取决于class属性中定义的是什么类型。项目启动的时候,会创建class指定类型的对象,并且子节点的名称并不是随便起的,是class中定义的这个类的变量名。子节点设置的值会通过类的set()方法进行属性的赋值

能够看见,项目启动时调用了set方法,并将配置文件中的值赋值给指定对象。

<logger>标签

一个logger标签就表示一个日志记录器,在同一个配置文件中可以有多个。他有三个属性 name、level 和 additivity,以及一个子节点<appender-ref>。

一、level可以设置日志级别

二、additivity默认为true,当logger匹配成功后,调用父logger的appender进行日志输出;

如果为false,则使用当前logger的appender进行日志输出。当additivity为true的时候,不要在当前logger引用<appender-ref>标签,否则会输出2次一样的日志。

三、name的值可以是类的全限定名称,也可以是包名,也可以是自定义的名称。

①上面的例子就是使用类的全限定名称。

②使用包名,这个包下所有类的日志都会被这个logger记录,并输出到控制台或者文件中。

③name使用自定义的名称,这个也是我工作中经常使用的。

定义一个客户端,并打印日志

java 复制代码
package com.travel.logback.study.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class WechatTerminalDemo {
    private Logger wechatLogger = LoggerFactory.getLogger("Wechat-UMR");
    @PostConstruct
    public void payAmount(){
        wechatLogger.debug("---远程接口调用成功--");
    }
}

添加日志记录器

XML 复制代码
    <logger name ="Wechat-UMR" level="debug" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

<root>标签

root标签也可以指定日志的级别,它是特殊的<logger>标签,是所有<logger>的祖先节点,等于<logger name="ROOT">。

一个配置文件中只能有一个root标签,且root标签只有一个属性level。因为name已经被命名为root,而且他是所有logger的祖先节点,所以也就没有additivity。

一个配置文件需要用<root>标签来进行兜底,避免日志出现丢失的情况。JAVA中定义的一个Logger对象,会向上遍历,直到在配置文件中找到合适的logger。如果一个logger都没找到,最终都会达到根logger,也就是root标签,所以,如果配置文件中没有声明root标签,那这个日志就会出现丢失。

比如,定义一个PayService,并且在一个方法中打印日志信息

java 复制代码
package com.travel.logback.study.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
public class PayService {
    private final Logger logger = LoggerFactory.getLogger(PayService.class);

    //该注解的作用是让Springboot启动时,自动执行该方法
    @PostConstruct
    private void pay() {
        logger.info("----支付成功----");
    }
}

logback.xml配置文件如下,logger日志记录器只配置了一个,使用全限定名称,没有root标签。

XML 复制代码
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <property name="UMR_LOG_STYLE" value="[%-5level] [%thread] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n" />

    <!-- 控制台输出设置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--使用上文定义的,全局的property配置-->
            <pattern>${UMR_LOG_STYLE}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <logger name ="com.travel.logback.study.web.DemoController" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

</configuration>

启动就会发现,没有root标签进行兜底,少了非常多的日志,只保留了 DemoController的日志。

3.3 排除某些依赖包中的日志

有些第三方的依赖包会打出许多我们不需要知道的INFO日志,不方便我们排查问题。

说是排除,实际就是使用类的全限定名称,创建一个日志记录器,将日志级别提高,不让他打印INFO级别的日志。

XML 复制代码
 <!--name写入类的全限定名称,level调整日志级别,additivity默认为true-->
 <logger name ="org.hibernate.boot.internal.SessionFactoryOptionsBuilder" level="ERROR" />

4、MDC的使用

MDC是 logback 提供的,用于日志跟踪的工具。多线程或者是并发量很大的情况下,请求没有唯一标识的话,不好排查问题,不清楚哪些日志对应的是哪一次请求,这个时候就需要用到MDC。

MDC 内部使用的是 ThreadLocal ,只有本线程才有效,如果在当前线程中,又开启了一个异步线程,那么异步线程的MDC就会丢失,异步线程获取指定key的时候就会是null。通常这种情况,都是请求传来的时候,携带一个requestId,用来表示唯一的请求,当前线程和异步线程就使用这唯一的requestId。

一般MDC是定义在拦截器中,等到一次完整的请求结束之后,再去清除掉,这里只是做一个Demo,所以我就不创建拦截器了。

java 复制代码
package com.travel.logback.study.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Date;


@RestController
public class DemoController {

    private Logger logger = LoggerFactory.getLogger(DemoController.class);

    //该注解的作用是让Springboot启动时,自动执行该方法
    @PostConstruct
    public void testLogback() {
        //往当前线程存入一个键值对
        MDC.put("traceId-UMR", "traceId" + new Date().getTime());
        logger.info("当前线程的traceId为:" + MDC.get("traceId-UMR"));
        logger.info("---info 级别的日志 ---");
        logger.warn("---warn 级别的日志 ---");
        //清除当前线程的traceId
        logger.warn("---清除当前线程的MDC ---");
        MDC.clear();
        logger.error("---error 级别的日志 ---");
    }
}

logback中,使用%X{key}来接收MDC中的值,key为存入MDC中的键

java 复制代码
    <!--%X{key}   %X表示输出MDC中特定键的值,key为具体的键名称,值不存在,则不会输出-->   
    <property name="NEW_LOG_STYLE" value="[%-5level] [%thread] [%logger] [%X{traceId-UMR}] [%d{yyyy-mm-dd H:mm:ss.sss}] [%c] [%M] [%L] %m%n" />

    <!-- 控制台输出设置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--使用上文定义的,全局的property配置-->
            <pattern>${NEW_LOG_STYLE}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <logger name ="com.travel.logback.study.web" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

由于打印error级别日志之前,我在代码中已经执行了clear()方法,将当前线程的所有key给清除掉了,所以,输出error日志到控制台的时候,显示是空白的。

5、注意事项

①logback单独使用,也可以通过logback.xml文件来完成一些个性化的配置,并不一定需要是Springboot项目。单独使用时,只需要引入slf4j和logback的包就可以了。

②日志工具的logger对象应当用private static final修饰,private保证不会被其他类调用,static final保证在类加载时就被初始化,并且其值不会改变,减少内存消耗。

③打印日志时,尽量不要使用"+"进行拼接,这会额外消耗内存,尽量使用{}占位符。

相关推荐
腥臭腐朽的日子熠熠生辉17 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian19 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之24 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端