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保证在类加载时就被初始化,并且其值不会改变,减少内存消耗。

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

相关推荐
念言-ny8 分钟前
springboot远程链接spark
spring boot·spark
我真不会起名字啊19 分钟前
QtJson数据格式处理详解
java·前端·javascript
硕风和炜30 分钟前
【LeetCode: 112. 路径总和 + 二叉树 + 递归】
java·算法·leetcode·面试·二叉树·递归
猩猩之火1 小时前
logback日志文件多环境配置路径
spring boot·logback
Xwzzz_1 小时前
基于Redisson实现重入锁
java·redis·lua
吴冰_hogan1 小时前
并发编程之CAS与Atomic原子操作详解
java·开发语言·数据库
Young丶2 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
风月歌2 小时前
基于Web的足球青训俱乐部管理后台系统的设计与开发源码(springboot+mysql+vue)
java·前端·spring boot·后端·mysql·mybatis·源码
小白起 v2 小时前
三天速成微服务
java·运维·微服务
叶 落2 小时前
Ubuntu 下载安装 Consul1.17.1
java·服务器·ubuntu·中间件·consul·配置中心