SpringBoot log configuration [detailed]

foreword

The log system is indispensable in the project. Currently, the more popular log frameworks include log4j and logback. You may not know that the author of these two frameworks is the same person. Logback is intended to be a follow-up version of the popular log4j project. Thereby recovering where log4j left off. In addition, slf4j (Simple Logging Facade for Java) is a log facade framework that provides commonly used interfaces in log systems, and logback and log4j implement slf4j. Our article will describe how to apply logback+slf4j in spring boot to realize log recording.

1. Why use Logback

Logback is a new generation of log framework developed by the author of the log4j framework. It is more efficient and can adapt to many operating environments. At the same time, it naturally supports SLF4J.
Logback is more flexible in customization, and it is also the built-in log framework of spring boot.

2. Logback use

2.1 Add dependencies

spring-boot-starter-logging is added to the maven dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

However, in actual development, we don't need to add this dependency directly. You will find that spring-boot-starter includes spring-boot-starter-logging, which is Spring Boot's default log framework Logback+SLF4J. And spring-boot-starter-web contains spring-boot-starte, so we only need to introduce web components:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 Default configuration

By default, Spring Boot outputs logs to the console and does not write to log files. If you want to write a log file other than console output, you need to set the logging.file or logging.path property in application.properties

注:二者不能同时使用,如若同时使用,则只有logging.file生效
logging.file=文件名
logging.path=日志文件路径

logging.level.包名=指定包下的日志级别
logging.pattern.console=日志打印规则

logging.file, setting file, can be an absolute path or a relative path. Such as: logging.file=my.log

logging.path, set the directory, the spring.log file will be created in this directory, and the log content will be written, such as: logging.path=/var/log

Note: The two cannot be used at the same time. If they are used at the same time, only logging.file will take effect. You can see that this method is simple to configure, but the functions that can be realized are also very limited. If you want more complex requirements, you need the following customization configured.

3. Detailed explanation of logback-spring.xml

Spring Boot officially recommends using a file name with -spring as your log configuration (for example, use logback-spring.xml instead of logback.xml), name the log configuration file logback-spring.xml, and put xml in src/main/resource below.

You can also use a custom name, such as logback-config.xml, just use logging.config=classpath:logback-config.xml to specify it in the application.properties file.

Before explaining log'back-spring.xml, let's understand three words:Logger, Appenders and Layouts(记录器、附加器、布局):Logback基于三个主要类:Logger,Appender和Layout。这三种类型的组件协同工作,使开发人员能够根据消息类型和级别记录消息,并在运行时控制这些消息的格式以及报告的位置。首先给出一个基本的xml配置如下:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO"/>

  <!-- Strictly speaking, the level attribute is not necessary since -->
  <!-- the level of the root level is set to DEBUG by default.       -->
  <root level="DEBUG">          
    <appender-ref ref="STDOUT" />
  </root>  
</configuration>

3.1 configuration elements

The basic structure of a logback.xml configuration file can be described as a configuration element, containing zero or more elements, followed by zero or more elements, followed by at most one element (or none). The following diagram illustrates this basic structure:
  insert image description here

3.2: The logger element

The logger element only accepts a required name attribute, an optional level attribute, and an optional additivity attribute, allowing values ​​of true or false. The value of the level attribute allows a case-insensitive string value TRACE, DEBUG, INFO, WARN, ERROR, ALL or OFF. The case-insensitive value INHERITED or its synonym NULL will force the logger's level to be inherited from a higher level in the hierarchy, and elements may contain zero or more elements; each appender so referenced is added to the specified In the logger, (note: the additivity attribute is described in detail below), the logger element level is inherited.

Example 1: In the example, only the root logger is assigned a level. This level value DEBUG is inherited by other loggers X, XY and XYZ
insert image description here
  Example 2: All loggers have a specified level value. Level inheritance doesn't work

insert image description here
  Example 3: Logger root, X and XYZ are assigned DEBUG, INFO and ERROR levels respectively. Logger XY inherits its level value from its parent X.
insert image description here
  Example 4: In Example 4, the loggers root and X are assigned DEBUG and INFO levels respectively. Loggers XY and XYZ inherit their level value from their nearest parent X which has the specified level.

insert image description here

3.3: The root element

The root element configures the root logger. It supports a single attribute, the level attribute. It does not allow any other attributes because the additivity flag does not apply to the root logger. Also, since the root logger has been named "ROOT", it also does not allow a name attribute. The value of the level attribute can be one of the case-insensitive strings TRACE, DEBUG, INFO, WARN, ERROR, ALL, or OFF The element can contain zero or more elements; each appender so referenced is added to the root record In the device (note: the additivity attribute is described in detail below).

3.4: appender element

The appender is configured using an element that takes two required attributes name and class. The name attribute specifies the name of the appender, and the class attribute specifies the fully qualified name of the appender class to instantiate. Elements can contain zero or one element, zero or more elements, and zero or more elements, the following diagram illustrates common structures:

insert image description here
Important: In logback, output destinations are called appenders, and the addAppender method adds appenders to a given logger. Each enabled logging request for a given logger will be forwarded to all appenders in that logger and higher up in the hierarchy. In other words, appenders are additively inherited from the logger hierarchy. For example, if you add a console appender to the root logger, all enabled logging requests will at least print on the console. If a file appender is additionally added to a logger (say L), logging requests enabled for L and L's children will be printed to the file and to the console. This default behavior can be overridden by setting the logger's additivity flag to false so that no appenders are added to the accumulation.

Appender is an interface, which has many sub-interfaces and implementation classes, as shown in the figure below:
  insert image description here
  The two most important Appenders are: ConsoleAppender and RollingFileAppender.

3.4.1:ConsoleAppender

ConsoleAppender, as the name suggests, outputs logs to the console.

3.4.2:RollingFileAppender

RollingFileAppender, a subclass of FileAppender, extends FileAppender and has the function of rolling log files. For example, a RollingFileAppender could log to a file called log.txt and change its logging target to another file once a certain condition is met.

There are two important subcomponents that interact with RollingFileAppender. The first RollingFileAppender subcomponent, the RollingPolicy is responsible for performing the operations required for rollover. The second subcomponent of RollingFileAppender, the TriggeringPolicy will determine if and when rollover occurs. So, what the RollingPolicy is responsible for and when the TriggeringPolicy is responsible for.

For any purpose, RollingFileAppender must have both RollingPolicy and TriggeringPolicy set. However, if its RollingPolicy also implements the TriggeringPolicy interface, it only needs to specify the former explicitly.

3.4.3: Rolling strategy

**TimeBasedRollingPolicy:** Probably the most popular rolling policy. It defines the rollover strategy based on time, such as by day or by month. TimeBasedRollingPolicy takes responsibility for rolling and triggering said rollover. In fact, TimeBasedTriggeringPolicy implements the RollingPolicy and TriggeringPolicy interfaces.
**SizeAndTimeBasedRollingPolicy:** Sometimes you may wish to archive files by date, but at the same time limit the size of each log file, especially if a post-processing tool imposes a size limit on the log files. To meet this requirement, logback provides SizeAndTimeBasedRollingPolicy, which is a subclass of TimeBasedRollingPolicy, which implements a rollover policy based on time and log file size.

3.5: The pattern element

The most important thing in the encoder is the pattern attribute, which is responsible for controlling the format of the output log. Here is an example written by myself:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n</pattern>

The output format after use is shown in the figure below
insert image description here
where:

%d{yyyy-MM-dd HH:mm:ss.SSS}: date
  %-5level: log level
  %highlight(): color, info is blue, warn is light red, error is bold red, debug is black
  %thread: The thread that prints the log
  % 15.15(): If the length of the recorded thread character is less than 15 (the first), it will be filled with spaces on the left, and if the character length is greater than 15 (the second), it will be truncated from the beginning Extra characters
  %logger: the class name of the log output
  %-40.40(): If the length of the recorded logger characters is less than 40 (the first), fill it with spaces on the right, if the length of the characters is greater than 40 (the second), Then truncate extra characters from the beginning
  %cyan: color
  %msg: log output content
  %n: newline character

3.6: The filter element

The two most important filters in filter are: LevelFilter, ThresholdFilter.

LevelFilter filters events based on exact level matches. If the event's level is equal to the configured level, the filter accepts or rejects the event, depending on the configuration of the onMatch and onMismatch properties. For example, the following configuration will only print INFO-level logs, and all other logs will be disabled:

<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>

ThresholdFilter filters events below a specified threshold. For events at or above the threshold, the ThresholdFilter will respond to NEUTRAL when its decision() method is called. However, events whose level is lower than the threshold will be rejected. For example, the following configuration will reject all logs lower than INFO level, and only output logs of INFO level and above:

<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>

4 Detailed logback-spring.xml example:

The above introduces several important elements in xml, and the xml I configured is posted below for reference (implemented a rollover strategy based on date and size, and differentiated output by INFO and ERROR logs, and standardized log output formats, etc.) :

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

    <!-- appender是configuration的子节点,是负责写日志的组件。 -->
    <!-- ConsoleAppender:把日志输出到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 默认情况下,每个日志事件都会立即刷新到基础输出流。 这种默认方法更安全,因为如果应用程序在没有正确关闭appender的情况下退出,则日志事件不会丢失。
         但是,为了显着增加日志记录吞吐量,您可能希望将immediateFlush属性设置为false -->
        <!--<immediateFlush>true</immediateFlush>-->
        <encoder>
            <!-- %37():如果字符没有37个字符长度,则左侧用空格补齐 -->
            <!-- %-37():如果字符没有37个字符长度,则右侧用空格补齐 -->
            <!-- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符 -->
            <!-- %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符 -->
            <!-- %msg:日志打印详情 -->
            <!-- %n:换行符 -->
            <!-- %highlight():转换说明符以粗体红色显示其级别为ERROR的事件,红色为WARN,BLUE为INFO,以及其他级别的默认颜色。 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n</pattern>
            <!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- info 日志-->
    <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
    <!--             2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
    <appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志文件路径和名称-->
        <File>logs/project_info.log</File>
        <!--是否追加到文件末尾,默认为true-->
        <append>true</append>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch><!-- 如果命中ERROR就禁止这条日志 -->
            <onMismatch>ACCEPT</onMismatch><!-- 如果没有命中就使用这条规则 -->
        </filter>
        <!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
         RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
        作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
            <!-- 文件名:logs/project_info.2017-12-05.0.log -->
            <!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
            <fileNamePattern>logs/project_info.%d.%i.log</fileNamePattern>
            <!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
            如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
            <maxHistory>30</maxHistory>
            <!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
            <totalSizeCap>20GB</totalSizeCap>
            <!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!--编码器-->
        <encoder>
            <!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
            <!-- 记录日志的编码:此处设置字符集 - -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- error 日志-->
    <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_error.log -->
    <!--             2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
    <appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志文件路径和名称-->
        <File>logs/project_error.log</File>
        <!--是否追加到文件末尾,默认为true-->
        <append>true</append>
        <!-- ThresholdFilter过滤低于指定阈值的事件。 对于等于或高于阈值的事件,ThresholdFilter将在调用其decision()方法时响应NEUTRAL。 但是,将拒绝级别低于阈值的事件 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level><!-- 低于ERROR级别的日志(debug,info)将被拒绝,等于或者高于ERROR的级别将相应NEUTRAL -->
        </filter>
        <!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
        RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
       作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
            <!-- 文件名:logs/project_error.2017-12-05.0.log -->
            <!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
            <fileNamePattern>logs/project_error.%d.%i.log</fileNamePattern>
            <!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
            如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
            <maxHistory>30</maxHistory>
            <!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
            <totalSizeCap>20GB</totalSizeCap>
            <!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!--编码器-->
        <encoder>
            <!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
            <!-- 记录日志的编码:此处设置字符集 - -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender(不用在意level值)。
    换句话说,appender是从记录器层次结构中附加地继承的。
    例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。
    如果另外将文件追加器添加到记录器(例如L),则对L和L'子项启用的记录请求将打印在文件和控制台上。
    通过将记录器的additivity标志设置为false,可以覆盖此默认行为,以便不再添加appender累积-->
    <!-- configuration中最多允许一个root,别的logger如果没有设置级别则从父级别root继承 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>

    <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
    <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
    <logger name="com.sailing.springbootmybatis" level="INFO">
        <appender-ref ref="info_log" />
        <appender-ref ref="error_log" />
    </logger>

    <!-- 利用logback输入mybatis的sql日志,
    注意:如果不加 additivity="false" 则此logger会将输出转发到自身以及祖先的logger中,就会出现日志文件中sql重复打印-->
    <logger name="com.sailing.springbootmybatis.mapper" level="DEBUG" additivity="false">
        <appender-ref ref="info_log" />
        <appender-ref ref="error_log" />
    </logger>

    <!-- additivity=false代表禁止默认累计的行为,即com.atomikos中的日志只会记录到日志文件中,不会输出层次级别更高的任何appender-->
    <logger name="com.atomikos" level="INFO" additivity="false">
        <appender-ref ref="info_log" />
        <appender-ref ref="error_log" />
    </logger>
</configuration>

5. Additional content

5.1:Let’s talk about the log output code here. Generally, someone may use the following methods to output in the code:

Object entry = new SomeObject(); 
logger.debug("The entry is " + entry);

5.2:There seems to be no problem with the above, but there will be the cost of constructing message parameters, that is, converting the entry into a string and adding it. And it is the same regardless of whether the message is recorded, that is, even if the log level is INFO, the operations in the brackets will be executed, but the log will not be output. The following is the optimized writing method:

if(logger.isDebugEnabled()) {
    
     
    Object entry = new SomeObject(); 
    logger.debug("The entry is " + entry);
}

5.3:In the writing method of 5.2, the set log level is first judged, and if it is in debug mode, the parameters are constructed, which improves the first writing method. But there is a better way to write it, using placeholders:

Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);

Only after evaluating whether to log, and only if the decision is yes, the logger implementation will format the message and replace the "{}" pairs with the string value of the entry. In other words, this form incurs no cost of parameter construction when logging statements are disabled.

The logback author conducted a test and found that the first and third writing methods will produce exactly the same output. However, with logging statements disabled, the third variant outperforms the first variant by at least 30 times.

If there are multiple parameters, write as follows:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

If you need to pass three or more parameters, you can also use the Object[] variant:

Object[] paramArray = {
    
    newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

5.4:When logging, we may need to record the abnormal stack information in the file. After testing, logger.error(e) will not print out the stack information. The correct way to write it is:

logger.error("程序异常, 详细信息:{}", e.getLocalizedMessage() , e);

Guess you like

Origin blog.csdn.net/qq_27480007/article/details/129124150