Java Spring Boot logback 日志配置与使用总结

发布时间 2023-12-06 16:14:13作者: 进击的davis

在项目开发中,日志是必不可少的,没有日志,怎么排查bug,而且日志也有助于我们看到相关的输入输出,总的来说,日志是日常项目开发必须要有的。今天我们学习 Spring Boot 中集成 logback 日志,这里主要会涉及到 日志的配置 和简单实现,更多的细节请移步官方文档,自己品读,此文档有助于初涉 Spring Boot & logback 人群,可以快速直接上手 web 项目。

环境说明:

  • Spring Boot: 3.2.0
  • jdk: 17

1.logback简介

之前我们分享过 log4j 的使用,其实 log4j 和 logback 都是一拨人开发的,相当于 logback 是 log4j 的进阶版、升级版,这两种日志框架也都是基于 slf4j(Simple log facade for Java) 接口实现的,而且在 Spring Boot 中 logback 也是内置的,这点我们从源码就可以看到:

image.png

Why logback

现在 logback 应用也广,为何呢[1]:

  • 1.更快的执行速度。基于 Log4J , Logback 重写了内部实现,在一定场景下,速度快10倍,而且需要的内存更少。
  • 2.测试充分。据说 这框架测试很充分,而且和 Log4j 不是一个级别,咱们也不知道,暂且相信吧。

主要组成

Logback 主要由三个模块组成:

  • logback-core,基础组件,其他模块基于此构建,提供一些关键的通用机制
  • logback-classic,相当于 Log4J 的升级版,实现了 SLF4J
  • logback-access,与 Servlet 交互的容器,如 tomcat、jetty 等,提供一些 HTTP 访问功能

2.配置项

通常我们会在 resources 目录下,和 application.properties 同级目录,另外起一个 日志 的配置文件,文件名为 logback-spring.xml ,当然你也可以在 application.properties或者application.yaml 中定义 logging 项,但不方便做到像在 xml 文件中定义的那么细,即在主配置文件中的配置,是个比较通用的,粗粒度的,如果想要更加细粒度的,比如设置多个输出项,不同的 format ,不同的 Level 输出,这种细粒度的,建议就在 logback-spring.xml 设置好了。

另外,总体来说,Logback 毕竟在 Log4J 基础上升级开发,所以其配置和 Log4J 的配置很相近,如果你了解过 Log4J 的配置,那么这里的配置对于你来说就是一通百通了,需要了解 Log4J 的配置,请参考[2]。

Logback 配置结构

<configuration scan="true" scanPeriod="60 seconds" debug="false">  
    <property name="xxx" value="yyy" /> 
    
    <contextName>${xxx}</contextName>
    
    <appender>
        //xxxx
    </appender>   
    
    <logger>
        //xxxx
    </logger>
    
    <root>             
       //xxxx
    </root>  
</configuration>  

可以看出主要就是在标签 configuration 下有3个 子元素:

  • appender
  • logger
  • root

接下来,我们就主要的标签元素做相应说明[4]。

日志标签元素

configuration

这是一个根节点,其中的各个属性如下:

  1. scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  2. scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  3. debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

property

标记一个上下文变量,属性有name和value,定义变量之后可以使用${}来获取。

contextName

标识一个上下文名称,默认为default,一般用不到

appender

用来格式化日志输出节点,有两个属性nameclass,class用来指定哪种输出策略,常用就是控制台输出策略文件输出策略

这个节点很重要,通常的日志文件需要定义三个appender,分别是控制台输出,常规日志文件输出,异常日志文件输出。

logger

可选节点,用来具体指明包的日志输出级别,它将会覆盖root的输出级别。 该节点有几个重要的属性如下:

  1. name:指定的包名
  2. level:可选,日志的级别,允许一个不区分大小写的字符串值 TRACE,DEBUG,INFO,WARN,ERROR,ALL 或 OFF
  3. addtivity:可选,默认为true,将此logger的信息向上级传递,将有root节点定义日志打印。如果设置为false,将不会上传,此时需要定义一个appender-ref 节点才会输出。

<logger>元素可以包含零个或多个<appender-ref>元素。

这样引用的每个 appender 都被添加到指定的 logger 中,logger 元素级别具有继承性。

某些应用场景:如单独把 service 包下的日志输出到 service.log 中,或者是单独处理切面日志。

root

这是一个必须节点,用来指定基础的日志级别,只有一个level属性,默认值是DEBUG。 该节点可以包含零个或者多个元素,子节点是appender-ref,标记这个appender将会添加到这个logger中。

springProperty

从属性文件在properties/yml文件中找到对应的配置项。

encoder

和pattern节点组合用于具体输出的日志格式和编码方式。

pattern

确定日志输出的格式。

filter

日志输出拦截器,没有特殊定制一般使用系统自带的即可,但是如果要将日志分开,比如将ERROR级别的日志输出到一个文件中,将除了ERROR级别的日志输出到另外一个文件中,此时就要拦截ERROR级别的日志了。

或者从另外的角度看,filter 元素中主要有2个过滤器:

  • LevelFilter,只匹配某种级别的日志,即精确匹配日志级别来过滤
  • ThresholdFilter,过滤某些低于该级别的日志,其他日志等于或高于的日志级别保留

其中还有几个子元素:

  • onMatch
  • onMisMatch
    在满足匹配或满足不匹配下分别执行什么动作,如 DENY/ACCEPT 等。

e.g.

只打印 INFO 级别的日志,其余的全部禁止打印输出:

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>INFO</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

拒绝所有低于 INFO 级别的日志,只输出 INFO 以及以上级别的日志:

<configuration>
  <appender name="CONSOLE"
    class="ch.qos.logback.core.ConsoleAppender">
    <!-- deny all events with a level below INFO, that is TRACE and DEBUG -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>INFO</level>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

rollingPolicy

日志回滚策略,如下,我们看到有多种日志滚动策略,看命名就知道,详细可以看源码:logback-core-1.4.11

image.png

TimeBasedRollingPolicy:可能是最受欢迎的滚动策略。它根据时间定义翻转策略,例如按天或按月。
TimeBasedRollingPolicy 承担滚动和触发所述翻转的责任。实际上,TimeBasedTriggeringPolicy 实现了 RollingPolicy 和 TriggeringPolicy 接口。

SizeAndTimeBasedRollingPolicy:有时您可能希望按日期归档文件,但同时限制每个日志文件的大小,特别是如果后处理工具对日志文件施加大小限制。
为了满足此要求,logback 提供了 SizeAndTimeBasedRollingPolicy ,它是 TimeBasedRollingPolicy 的一个子类,实现了基于时间和日志文件大小的翻滚策略。

file

节点用来指明日志文件的输出位置,可以是绝对路径也可以是相对路径。

fileNamePattern

指明日志存储格式,可以连路径一起。

maxHistory

可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件,,例如设置为30的话,则30天之后,旧的日志就会被删除,此处设置和 fileNamePattern 的设置有关,比如我们在 case 中设置的 <fileNamePattern>${filePath}/myDemo.info.%d{yyyy-MM-dd}.log</fileNamePattern> ,那么我们这里设置的整型数值表示天,按天滚动,如果我们设置的是 <fileNamePattern>${filePath}/myDemo.info.%d{yyyy-MM-dd-HH}.log</fileNamePattern>maxHistory 设置整型数值时,这里就表示的是 按小时滚动,所以到底是按 天 滚动还是按 小时 滚动,取决于我们设置的 fileNamePattern 的最后一位是 dd 还是 HH

maxFileSize
每个日志文件的大小容量。

totalSizeCap

可选节点,用来指定日志文件的上限大小,例如设置为3GB的话,那么到了这个值,就会删除旧的日志。

3.集成配置

我们给日志加点色彩输出,这里的 demo 的主配置文件 application.yaml

server:
  port: 8000

# 彩色输出
spring:
  output:
    ansi:
      enabled: detect

控制台输出

logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!-- scan scanPeriod debug -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <springProperty scope="context" name="filePath" source="logging.file.path"/>
    <!-- contextName 区分不同应用程序 -->
    <contextName>myDemo</contextName>
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) %F:%M:%L - %magenta(%msg) %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <root level="info">
        <appender-ref ref="consoleLog"/>
    </root>

</configuration>

输出到日志文件

logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!-- scan scanPeriod debug -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- property -> springProperty, 属性文件:在properties文件中找到对应的配置项 -->
    <springProperty scope="context" name="filePath" source="logging.file.path"/>
    <!-- contextName 区分不同应用程序 -->
    <contextName>myDemo</contextName>
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) %F:%M:%L - %magenta(%msg) %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--根据日志级别分离日志,分别输出到不同的文件-->
    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %F:%M:%L - %msg%n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--按时间保存日志 修改格式可以按小时、按天、月来保存-->
            <fileNamePattern>${filePath}/myDemo.info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--保存时长-->
            <MaxHistory>30</MaxHistory>
            <!-- 每个文件最大容量 -->
            <maxFileSize>100MB</maxFileSize>
            <!--文件总大小,到上限后删除日志-->
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>


    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileInfoLog"/>
    </root>

</configuration>

输出异常到日志文件

logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!-- scan scanPeriod debug -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- property -> springProperty, 属性文件:在properties文件中找到对应的配置项 -->
    <springProperty scope="context" name="filePath" source="logging.file.path"/>
    <!-- contextName 区分不同应用程序 -->
    <contextName>myDemo</contextName>
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) %F:%M:%L - %magenta(%msg) %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--根据日志级别分离日志,分别输出到不同的文件-->
    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %F:%M:%L - %msg%n
            </pattern>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--路径-->
            <fileNamePattern>${filePath}/myDemo.error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>30</MaxHistory>
            <!--文件大小-->
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileErrorLog"/>
    </root>

</configuration>

常用输出

logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!-- scan scanPeriod debug -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- property -> springProperty, 属性文件:在properties文件中找到对应的配置项 -->
    <property name="logging.file.path" value="./logs" />
    <springProperty scope="context" name="logging.file.path" source="logging.file.path"/>
    <!-- contextName 区分不同应用程序 -->
    <contextName>myDemo</contextName>
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) %F:%M:%L - %magenta(%msg) %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--根据日志级别分离日志,分别输出到不同的文件-->
    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %F:%M:%L - %msg%n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--按时间保存日志 修改格式可以按小时、按天、月来保存-->
            <fileNamePattern>${logging.file.path}/myDemo.info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--保存时长-->
            <MaxHistory>90</MaxHistory>
            <!--文件总大小-->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %F:%M:%L - %msg%n
            </pattern>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--路径-->
            <fileNamePattern>${logging.file.path}/myDemo.error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>90</MaxHistory>
            <!--文件总大小-->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileInfoLog"/>
        <appender-ref ref="fileErrorLog"/>
    </root>
</configuration>

输出特定目录下日志

比如有时我们的需求更加细化,希望将 service 包下的日志集中在 service.log 中,这里又应该如何操作呢,具体请参考[5],service.log 部分。

输出切面日志-请求响应参数或traceId

对于 web应用 最常见的日志需求,我们肯定需要将请求的入参和响应的参数及相关的一些客户端信息打印在一起,或者,综合那么多日志,都是分散的,我们想通过一个参数就追踪到某个请求相关的一系列日志,即日志追踪,这里也请参考[5],http-log部分。

4.日志的使用

测试 controller:

package com.example.springbootforlogbackdemo;

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

import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
public class LoggerUsageController {
    private static final Logger logger = LoggerFactory.getLogger(LoggerUsageController.class);

    public static String timestampToFormatDatetime() {
        long ts = System.currentTimeMillis();
        Date date = new Date(ts);
        String formatTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(date);
        return formatTime;
    }

    @GetMapping(value = "/logUsage")
    public Object logUsage() {
        logger.debug("this is a debug msg."); // 低级别日志,不输出

        logger.info("this is a info msg."); // 输出到 console 和 info日志
        logger.warn("this is a warn msg.");

        logger.error("this is a error msg."); // 输出到 console 和error日志

        return "当前时间:" + LoggerUsageController.timestampToFormatDatetime();
    }

}

测试结果

控制台:

image.png

fileInfo:

image.png

fileError:

image.png

参考: