Appender

我们在基础配置中简单介绍了appender的配置以及用法,那么这篇我们就来进阶了解一下appender!

回顾一下:什么是Appender

我们在上一篇基本配置的介绍中,我们知道logback 将写入日志事件的任务委托给一个名为 appender 的组件,所以appender是负责日志的输出或者打印事宜。以及大致有一个印象,日志的打印和一个叫doAppend()的方法有关。这篇我们就具体谈谈这些。

源码出发

appender起源在哪里呢?我们通过官方的文档可以知道最顶级的接口Appender是这一切的开端。

public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable<E> {

    /**
     * Get the name of this appender. The name uniquely identifies the appender.
     */
    String getName();

    /**
     * This is where an appender accomplishes its work. Note that the argument 
     * is of type Object.
     * @param event
     */
    void doAppend(E event) throws LogbackException;

    /**
     * Set the name of this appender. The name is used by other components to
     * identify this appender.
     * 
     */
    void setName(String name);

}

doAppender() 方法接收一个泛型参数 E 作为唯一的参数。E 的实际参数类型取决于 logback 模块。在 logback-classic 模块里面,E 的类型是 ILoggingEvent 。在 logback-access 模块里面,E 的类型是 AccessEventdoAppend() 是 logback 框架里面最重要的模块。它的责任是将日志事件进行格式化,然后输出到对应的设备上。我们以logback为例打开源码LoggingEvent这个类,这些属性就是E的内容,我们这里大致只要先知道里面存的是日志内容相关的信息。

两个顶级抽象类:AppenderBase ,UnsynchronizedAppenderBase,在官方文档中对AppenderBase的介绍有一句话

It is the super-class of all appenders shipped with logback.

这个不够全面,应该要包含UnsynchronizedAppenderBase,因为这两个加起来才是logback中所有的具体的appender的父类。

因为AppenderBase 的实现是 synchronized 的。不同的线程通过同一个 appender 打印日志是线程安全的。当一个线程 T 正在执行 doAppend() 方法,接下来其它的线程调用将会被阻塞直到线程 T 离开 doAppend() 方法,这样可以确保 T 对 appender 的访问具有独占性。这种日志打印的方式其实对我们线上项目是不够友好的,很多时候我们需要的是多线程的打印方式,UnsynchronizedAppenderBase能够很好满足我们的需求,所以我们的介绍也会以实现UnsynchronizedAppenderBase的appender为介绍对象。

OutputStreamAppender

OutputStreamAppender将事件附加到 java.io.OutputStream 上。这个类提供了其它 appender 构建的基础服务。用户通常不会直接实例一个 OutputStreamAppender 实例。因为一般来说 java.io.OutputStream 类型不能方便的转为 String。因为在配置文件中没有方法去直接指定一个 OutputStream 目标对象。简单来说,你不能通过配置文件配置一个 OutputStreamAppender。但是这并不意味着 OutputStreamAppender 缺少配置属性。

属性名属性值描述
ecoderEncoder决定通过哪种方式将事件写入 OutputStreamAppender,Encoder 将会在单独的章节介绍
immediateFlushbooleanimmediateFlush 的默认值为 true。立即刷新输出流可以确保日志事件被立即写入,并且可以保证一旦你的应用没有正确关闭 appender,日志事件也不会丢失。从另一方面来说,设置这个属性为 false,有可能会使日志的吞吐量翻两番(视情况而定)。但是,设置为 false,当应用退出的时候没有正确关闭 appender,会导致日志事件没有被写入磁盘,可能会丢失。

这个appender并不是我们需要具体了解的,我们主要了解他的子类。

OutputStreamAppender 是其他三个 appender 的父类,分别是 ConsoleAppenderFileAppender 以及 RollingFileAppenderFileAppender 又是 RollingFileAppender 的父类。这三个才是我们真正要具体掌握的!

OutputStreamAppender的继承关系类图
OutputStreamAppender的继承关系类图

ConsoleAppender

ConsoleAppender将日志事件附加到控制台,更具体来说其实是通过 System.out 或者 System.err 来进行输出。默认通过前者。ConsoleAppender 通过用户指定的 encoder,格式化日志事件。Encoder 会在接下来的章节讨论。System.outSystem.err 两者都是 java.io.PrintStream 类型。因此,它们被包装在可以进行 I/O 缓存操作的 OutputStreamWriter 中。

属性名类型描述
encoderEncoderOutputStreamAppender 属性
targetStringSystem.outSystem.err。默认为 System.out
withJansibooleanwithJansi 的默认值为 false。设置 withJansitrue 可以使用 ANSI 彩色代码。在 windows 上如果设置为 true,你应该将 org.fusesource.jansi:jansi:1.9 这个 jar 包放到 classpath 下。基于 Unix 实现的操作系统,像 Linux、Max OS X 都默认支持 ANSI 才彩色代码。

示例配置:

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" >
        <!-- encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
        <!--<target>System.err</target>-->
        <!--<withJansi>true</withJansi>-->
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

FileAppender

FileAppenderOutputStreamAppender 的子类,将日志事件输出到文件中。通过 file 来指定目标文件。如果该文件存在,根据 append 的值,要么将日志追加到文件中,要么该文件被截断。

属性名类型描述
appendboolean如果为 true,日志事件会被追加到文件中,否则的话,文件会被截断。默认为 true
encoderEncoder参见 OutputStreamAppender 的属性
fileString要写入文件的名称。如果文件不存在,则新建。在 windows 平台上,用户经常忘记对反斜杠进行转义。例如,c:temptest.log 不会被正确解析,因为 't' 是一个转义字符,会被解析为一个 tab 字符 (u0009)。正确的值应该像:c:/temp/test.log 或者 c:\temp\test.log。没有默认值。
prudentboolean如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作,效率。默认是 false。

例如

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>testFile.log</file>
        <!--         将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
        <immediateFlush>true</immediateFlush>
        <!--         默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="FILE" />
    </root>
</configuration>

如上配置会在当前项目目录下生成一个testFile.log文件,内容为:

250  [main] INFO  com.exercise.appenderconfig.MyApp - hello,now is 2020-01-08T20:07:16.236

RollingFileAppender

RollingFileAppender继承自FileAppender,具有轮转日志文件的功能。白话文来说:RollingFileAppender 将日志输出到 log.txt 文件,在满足了特定的条件之后,比如第二天把前一天的日志打包起来,或者将日志放到另一个地方。

RollingFileAppender 进行交互的有两个重要的子组件。第一个是 RollingPolicy,轮转策略,它负责日志轮转的功能,即轮转日志做什么事情;另一个是 TriggeringPolicy,触发策略,它负责日志轮转的时机,即什么条件下进行轮转。

为了让 RollingFileAppender 生效,必须同时设置 RollingPolicy TriggeringPolicy。但是,如果 RollingPolicy 也实现了TriggeringPolicy 接口,那么只需要设置前一个就好了。

RollingFileAppender 的属性如下所示:

属性名类型描述
fileString参见 FileAppender
appendboolean参见 FileAppender
encoderEncoder参见 OutputStreamAppender
rollingPolicyRollingPolicy当轮转发生时,指定 RollingFileAppender 的行为
triggeringPolicyTriggeringPolicy告诉 RollingFileAppender 什么时候发生轮转行为
prudentbooleanFixedWindowRollingPolicy 不支持该属性。 其它的参考 FileAppender

Rolling policy 简介

RollingPolicy 负责轮转的方式为:移动文件以及对文件改名。

接口源码如下:

package ch.qos.logback.core.rolling;  

import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.spi.LifeCycle;

public interface RollingPolicy extends LifeCycle {

    public void rollover() throws RolloverFailure;
    public String getActiveFileName();
    public CompressionMode getCompressionMode();
    public void setParent(FileAppender appender);
}

rollover 方法负责对日志文件进行归档。getActiveFileName() 方法负责获取当前日志文件的名字。getCompressionMode 方法决定采取哪种压缩模式。通过 setParent 方法引用父类。

常用的轮转模式:TimeBasedRollingPolicy

它是基于时间来定义轮转策略。例如按天或者按月。TimeBasedRollingPolicy 既负责轮转的行为,也负责触发轮转。实际上,TimeBasedRollingPolicy 同时实现了 RollingPolicyTriggeringPolicy 接口。

TimeBasedRollingPolicy 的配置中 fileNamePattern是必选项,其它的属性可选。

属性名类型描述
fileNamePatternString该属性定义了轮转时的属性名。它的值应该由文件名加上一个 %d 的占位符。%d 应该包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略日期格式,默认为 yyyy-MM-dd。我们一般会在RollingFileAppender指定当前日志的文件名,触发轮转后将之前的日志进行转储。注意:斜杆 '/' 或者反斜杠 '' 都会被解析成目录分隔符。 可以指定多个 %d,但是只能有一个是主要的,用于推断轮转周期。其它的 %d 占位符必须通过 'aux' 标记为辅助的。
maxHistoryint这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。比如,你指定按月轮转,指定 maxHistory = 6,那么 6 个月内的归档文件将会保留在文件夹内,大于 6 个月的将会被删除。注意:当旧的归档文件被移除时,当初用来保存这些日志归档文件的文件夹也会在适当的时候被移除。
totalSizeCapint这个可选属性用来控制所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。使用这个属性时还需要设置 maxHistory 属性。maxHistory 优先级会高于totalSizeCap。
cleanHistoryOnStartboolean如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。 归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作。

该模式的轮转周期是通过 fileNamePattern 推断出来的。在某些情况下,你可能想要根据时区而不是主机的时钟来轮转日志,具体见官方文档,这里不做叙述。

TimeBasedRollingPolicy 支持文件自动压缩。如果 fileNamePattern.gz 或者 .zip 结尾,将会启动这个特性。

下面是关于 fileNamePattern 的一些常见示例:

fileNamePattern轮转周期示例
/wombat/foo.%d每天轮转(晚上零点)。日期格式默认为 yyyy-MM-dd没有设置 file 属性:在 2006.11.23 这一天的日志都会输出到 /wombat/foo.2006-11-23 这个文件。晚上零点以后,日志将会输出到 wombat/foo.2016-11-24 这个文件。 设置 file 的值为 /wombat/foo.txt:在 2016.11.23 这一天的日志将会输出到 /wombat/foo.txt 这个文件。在晚上零点的时候,foo.txt 将会被改名为 /wombat/foo.2016-11-23。然后将创建一个新的 foo.txt,11.24 号这一天的日志将会输出到这个新的文件中。
/wombat/%d{yyyy/MM}/foo.txt每个月开始的时候轮转没有设置 file 属性:在 2016.10 这一个月中的日志将会输出到 /wombat/2006/10/foo.txt。在 10.31 晚上凌晨以后,11 月份的日志将会被输出到 /wombat/2006/11/foo.txt。 设置 file 的值为 /wombat/foo.txt:在 2016.10,这个月份的日志都会输出到 /wombat/foo.txt。在 10.31 晚上零点的时候,/wombat/foo.txt 将会被重命名为 /wombat/2006/10/foo.txt,并会创建一个新的文件 /wombat/foo.txt ,11 月份的日志将会输出到这个文件。依此类推。
/wombat/foo.%d{yyyy-ww}.log每周的第一天(取决于时区)每次轮转发生在每周的第一天,其它的跟上一个例子类似
/wombat/foo%d{yyyy-MM-dd_HH}.log每小时轮转跟之前的例子类似
/wombat/foo%d{yyyy-MM-dd_HH-mm}.log每分钟轮转跟之前的例子类似
/wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.log每分钟轮转跟之前的例子类似,不过时间格式是 UTC
/foo/%d{yyyy-MM, aux}/%d.log每天轮转。归档文件在包含年月的文件夹下第一个 %d 被辅助标记。第二个 %d 为主要标记,但是日期格式省略了。因此,轮转周期为每天(由第二个 %d 控制),文件夹的名字依赖年与月。例如,在 2016.11 的时候,所有的归档文件都会在 /foo/2006-11/ 文件夹下,如:/foo/2006-11/2006-11-14.log
/wombat/foo.%d.gz每天轮转(晚上零点),自动将归档文件压缩成 GZIP 格式file 属性没有设置:在 2009.11.23,日志将会被输出到 /wombat/foo.2009-11-23 这个文件。但是,在晚上零点的时候,文件将会被压缩成 /wombat/foo.2009-11-23.gz。在 11.24,这一天的日志将会被直接输出到 /wombat/folder/foo.2009-11-24 这个文件。
file 属性的值设置为 /wombat/foo.txt:在 2009.11.23,日志将会被输出到 /wombat/foo.txt 这个文件。在晚上零点的时候,该文件会被压缩成 /wombat/foo.2009-11-23.gz。并会创建一个新的 /wombat/foo.txt 文件,11.24 这一天的日志将会被输出到该文件。依此类推。

注意:轮转并不是时间驱动的,而是依赖日志事件。例如,在 2020.01.08,假设 fileNamePattern 的值为 yyyy-MM-dd(按天轮转),在晚上零点之后,没有日志事件到来,假设在 23 分 47 秒之后,第一个到达的日志事件将会触发轮转。也就是说轮转实际发生在 01.09 00:23'47 AM 而不是 0:00 AM。因此,依赖日志事件的到达速度,所以轮转可能会有延迟。但是!不管延迟的情况是什么样,一定周期内生成的日志事件将会被输出到指定的文件中。

示例:

<configuration>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logFile.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>logFile.%d{yyyy-MM-dd}.gz</FileNamePattern>
            <MaxHistory>180</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="FILE" />
    </root>
</configuration>

基于大小以及时间的轮转策略:SizeAndTimeBasedRollingPolicy

有时你希望按时轮转,但同时又想限制每个日志文件的大小。特别是如果后期处理工具需要对日志进行大小限制。为了满足这个需求,logback 配备了 SizeAndTimeBasedRollingPolicy

TimeBasedRollingPolicy 是所有的限制归档文件总大小。而SizeAndTimeBasedRollingPolicy是限制每个日子文件的大小。

例如:

<configuration debug="true">
    <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>mylog.txt</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--             按天轮转 -->
            <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>

        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="ROLLING" />
    </root>
</configuration>

SizeAndTimeBasedRollingPolicyfileNamePattern属性除了%d之外%i也是必要属性。在当前时间还没有到达周期轮转之前,日志文件达到了 maxFileSize 指定的大小,会进行归档,%i是轮转的索引,递增索引从 0 开始。如:mylog-2020-01-08.0.txtmylog-2020-01-08.1.txt

当然默认的轮转算法不止这些还有诸如:FixedWindowRollingPolicy(根据固定窗口算法重命名文件的轮转策略),SizeBasedTriggeringPolicy(观察当前活动文件的大小,如果已经大于了指定的值,它会给 RollingFileAppender 发一个信号触发对当前活动文件的轮转)的触发策略

如果这些常用的还不能满足你的需求也可以自定义appender~

Encoder

什么是Encoder

Encoder 将日志事件转换为字节数组,同时将字节数组写入到一个 OutputStream 中。Encoder 在 logback 0.9.19 版本引进,旨在代替layouts。现在的layouts都是通过LayoutWrappingEncoder来实现 encoder 与 layout 进行交互,它实现了 encoder 接口并且包裹了一个 layout,通过委托该 layout 将日志事件转换为字符串。

PatternLayoutEncoder 是目前真正唯一有用的 encoder。它仅仅包裹了一个 PatternLayout 就完成了大部分的工作。

源码出发

package ch.qos.logback.core.encoder;

public interface Encoder<E> extends ContextAware, LifeCycle {

    /**
     * Get header bytes. This method is typically called upon opening of 
     * an output stream.
     * 
     * @return header bytes. Null values are allowed.
     */
    byte[] headerBytes();

    /**
     * Encode an event as bytes.
     *  
     * @param event
     */
    byte[] encode(E event);
                    
    /**
     * Get footer bytes. This method is typically called prior to the closing 
     * of the stream where events are written.
     * 
     * @return footer bytes. Null values are allowed.
     */
    byte[] footerBytes();
}

我们可以看到这个接口其实很简单,但是可以做的事却远不只看到的如此!

PatternLayoutEncoder

由于 PatternLayout 是最常用的 layout,logback 使用 PatternLayoutEncoder 来满足这种用法。它扩展了 LayoutWrappingEncoder,被限制用来包裹 PatternLayout 实例。

在 logback 0.9.19 版本,无论 FileAppender 还是其子类通过 PatternLayout 来进行配置,都必须使用 PatternLayoutEncoder 来代替。

immediateFlush 属性

LOGBACK 1.2.0 中, immediateFlush 属性是 appender 的一部分。

用格式化字符串作为开头

为了帮助解析日志文件,logback 可以将格式化字符串插入到日志文件的顶部。这个功能默认是关闭的。可以为相关的 PatternLayoutEncoder 设置 outputPatternAsHeader 属性的值为 true 来开启这个功能。下面是示例:

<appender name="FILE" class="ch.qos.logback.core.FileAppender"> 
  <file>foo.log</file>
  <encoder>
    <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
    <outputPatternAsHeader>true</outputPatternAsHeader>
  </encoder> 
</appender>

将会在日志文件中输出类似下面的日志:

#logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again
...

以 "#logback.classic pattern" 开头的行就是新插入的行。

格式化语法:pattern

每一个转换说明符由一个百分号开始 '%',后面跟随可选的格式修改器,以及用综括号括起来的转换字符与可选的参数。转换字符需要转换的字段。如:logger 的名字,日志级别,日期以及线程名。格式修改器控制字段的宽度,间距以及左右对齐。

我们这里举几个例子,完整的请移步官网:http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout

作用pattern效果
输出 logger 的名字c{length}
lo{length}
logger{length}
logger 名字会根据length长度被缩写,但是最右边的部分永远不会被简写,即使它的长度比 length 的值要大,其它的部分可能会被缩短为一个字符,但是永不会被移除。设置 length 的值为 0 返回 logger 名字中最右边的点右边的字符。
上下文名称contextName
cn
输出日志事件附加到的 logger 上下文的名字。
日期d{pattern} date{pattern} d{pattern, timezone} date{pattern, timezone}用于输出日志事件的日期。日期转换符允许接收一个字符串作为参数。字符串的语法与 SimpleDateFormat 中的格式完全兼容。
行号L / line输出发出日志请求所在的行号。
生成行号不是特别快,谨慎使用
日志信息m / msg / message输出与日志事件相关联的,由应用程序提供的日志信息。
换行n输出平台所依赖的行分割字符。
转换字符提供了像 "n" 或 "rn" 一样的转换效果。
日志级别p / le / level输出日志事件的级别。
线程名t / thread输出生成日志事件的线程名。
MDCX{key:-defaultVal}
mdc{key:-defaultVal}
输出生成日志事件的线程的 MDC (mapped diagnostic context)。
如果 MDC 转换字符后面跟着用花括号括起来的 kye,例 %MDC{userid},那么 'userid' 所对应 MDC 的值将会输出。如果没有指定的 key,那么 MDC 的整个内容将会以 "key1=val1, key2=val2" 的格式输出。
类名C{length}
class{length}
输出发出日志请求的类的全限定名称。跟 %logger% 转换符一样。设置为0则不会打印包前缀。
生成调用者类的信息并不是特别快,谨慎使用

在给定的转换模式上下文中,% 有特殊的含义,如果作为字面量,需要进行转义。例如,"%d %p % %m%n"。

在大多数的情况下,字面量包括空格或者其它的分隔符,所以它们不会与转换字符混淆。例如,"%level [%thread] - %message%n" 包含字面量字符 " [" 与 "] - "。但是,如果一个转换字符后面紧跟着一个字面量,那么 logback 的模式解析器将会错误的认为这个字面量也是转换字符的一部分。例如,"%date%nHello" 将会被解析成两个转换字符 %date 与 %nHello,但是 %nHello 不是一个转换字符,所以 logback 将会输出 %PARSER_ERROR[nHello]。如果你想要区分 %n 跟 Hello,可以通过给 %n 传递一个空参数。例如,"%date%n{}Hello" 将会被解析为 %date %n 再紧跟着一个字符串 "Hello"。

格式修改器

有了pattern还不够,我们的每个日志的等级可能长可能短比如debug,info,就会造成日志打印的参差不齐,为了美观我们可以使用格式修改器,可以对每个数据字段进行对齐,以及更改最大最小宽度。

可选的格式修改器放在百分号跟转换字符之间。

第一个可选的格式修改器是左对齐标志,也就是减号 (-) 字符。接下来的是最小字段宽度修改器,它是一个十进制常量,表示输出至少多少个字符。如果字段包含很少的数据,它会选择填充左边或者右边,直到满足最小宽度。默认是填充左边 (右对齐),但是你可以通过左对齐标志来对右边进行填充。填充字符为空格。如果字段的数据大于最小字段的宽度,会自动扩容去容纳所有的数据。字段的数据永远不会被截断。

这个行为可以通过使用最大字段宽度修改器来改变,它通过一个点后面跟着一个十进制常量来指定。如果字段的数据长度大于最大字段的宽度,那么会从数据字段的开头移除多余的字符。

如果想从后面开始截断,可以在点后面增加一个减号。如果是这样的话,最大字段宽度是 8,数据长度是十个字符的长度,那么最后两个字符将会被丢弃。

格式修改器左对齐最小宽度最大宽度备注
%20loggerfalse20none如果 logger 的名字小于 20 个字符的长度,那么会在左边填充空格
%-20loggertrue20none如果 logger 的名字小于 20 个字符的长度,那么会在右边填充空格
%.30loggerNAnone30如果 logger 的名字大于 30 个字符的长度,那么从前面开始截断
%20.30loggerfalse2030如果 logger 的名字大于 20 个字符的长度,那么会从左边填充空格。但是如果 logger 的名字大于 30 字符,将会从前面开始截断
%-20.30loggertrue2030如果 logger 的名字小于 20 个字符的长度,那么从右边开始填充空格。但是如果 logger 的名字大于 30 个字符,将会从前面开始截断
%.-30loggerNAnone30如果 logger 的名字大于 30 个字符的长度,那么从后面开始截断

我们来尝试解读一个pattern吧!

%d{yyyy/MM/dd HH:mm:ss.SSS} %-5p [%t] [%X{requestId} - %C{0}] %m%n

打印的格式大致是日期+左对齐5格的日志等级+ 被[]括起来的线程名+MDC定义的key为requestId+没有包前缀的类名+日志信息+类名。

我们所列举的都是比较常见的打印格式,还有更多奇妙的操作,如打印日志的颜色,分组格式化等等可以具体查看官方文档深入了解!