Java 日志实现框架 Logback

2,177 阅读18分钟

前篇:java 生态下的日志框架

后篇:Logback 配置样例

简介

Log4j 的早期作者 Ceki Gülcü 于 2006年开发了 SLF4J(The Simple Logging Facade for Java) 门面框架作为 JCL(Jakarta Commons Logging) 的更可靠替代方案。

之后 Ceki Gülcü 又开发了新一代日志实现框架 Logback 作为 Slf4j 的实现。

与 Log4j2 的关系

Apache Log4j2 是 Apache Log4j 的升级产品,并且不兼容 Log4j,Log4j2 主要参考自 Logback

三个模块

  • logback-core:其它两个模块的基础模块
  • logback-classic:它是 log4j 的一个改良版本,同时它完整实现了 slf4j API 使你可以很方便地更换成其它日志系统如 log4j 或 JDK14 Logging
  • logback-access:访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能

日志请求级别与日志记录器级别

我们常说的日志级别其实表达并不准确,有时你说的可能是日志请求级别,有时候你说的说日志记录器的级别

日志请求级别

上图是我们最常见的用法,其中 .info() 、.error() 指的是向 logger 这个日志记录器,写入什么级别的日志

日志请求级别中的级别指的是日志信息的级别

级别

按照日志信息的“重要性”,从高到低分为

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE

日志记录器级别

按照打印日志的丰富程度,从多到少分为

  • ALL
  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR
  • OFF

其中 ALL 是打印所有日志信息, OFF 是什么都不打印

日志记录器

在回到上图,其中 logger 代表一个日志记录器org.slf4j.Logger是一个接口,logger 的实际类型要看你使用的是什么“日志实现框架”,如果用的是 logback 框架,那么该 logger 的实际类型为 ch.qos.logback.classic.Logger

日志记录器默认级别

如果愿意,你也可以在配置文件中更改根记录器的默认级别

LoggerContext

各个 logger 都被关联到一个 LoggerContext,LoggerContext 负责制造 logger,也负责以树结构排列各 logger。其他所有 logger 也通过 org.slf4j.LoggerFactory 类的静态方法 getLogger 取得。 getLogger 方法以 logger 名称为参数。用同一名字调用 LoggerFactory.getLogger 方法所得到的永远都是同一个 logger 对象的引用。

不同级别的日志记录器能够记录的日志信息

第一行是日志记录器的级别,其中LOG_LEVEL列是日志请求的级别,YES 表示能打印,NO 表示不能打印

LOG_LEVELTRACEDEBUGINFOWARNERROROFF
TRACEYESNONONONONO
DEBUGYESYESNONONONO
INFOYESYESYESNONONO
WARNYESYESYESYESNONO
ERRORYESYESYESYESYESNO

参考

使用 Logback

官方文档

配置方式

Logback 提供两种配置方式

  1. java编程
  2. 配置文件
    1. XML 配置文件
    2. Groovy 配置文件

配置文件的加载顺序

Logback 会按照以下顺序查找配置文件

  1. 首先在 classpath 中查找 logback-test.xml,如果找不到
  2. Logback 会在 classpath 中查找 logback.groovy,如果找不到
  3. Logback 会在 classpath 中查找 logback.xml,如果找不到
  4. Logback 将进行自动配置

默认配置

即使你不进行任何配置,Logback 也会进行默认配置,那么默认配置配置了那些东西呢?

如果用 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>

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

状态管理器 —— StatusManager

这里的说的“状态”是官方的说法,对应到具体的事物就是 日志消息

Logback 将其内部状态数据收集到 StatusManager 对象中,并通过 LoggerContext 进行访问,通过 StatusManager,您可以访问与logback上下文关联的所有状态数据。

当然,不可保留所有的日志消息,为了将内存使用保持在合理的水平,默认的 StatusManager 实现将状态消息存储在两个单独的部分中:头部分和尾部分。

头部分存储前 H 个状态消息,而尾部分存储最后 T 个消息。目前 H=T=150。注意,这些值在将来的版本中可能会改变。

经典的Logback附带了一个称为ViewStatusMessagesServlet的servlet。此Servlet将StatusManager与当前内容关联 的内容打印 LoggerContext为HTML表。这是示例输出。

Logback-classic 还附带一个名为 ViewStatusMessagesServlet 的 servlet。此 servlet 将 StatusManager 与当前 LoggerContext 关联的内容输出为 html。

输出示例

要将此servlet添加到您的Web应用程序,请将以下行添加到其WEB-INF/web.xml文件。

  <servlet>
    <servlet-name>ViewStatusMessages</servlet-name>
    <servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ViewStatusMessages</servlet-name>
    <url-pattern>/lbClassicStatus</url-pattern>
  </servlet-mapping>

以上是官方给的方法,下面是在 spring 中配置的方法,只需要将 @Bean 加入到任意 spring 组件中即可生效

@Component
public class ServletRegister {

    @Bean
    public ServletRegistrationBean registerServlet() {
        return new ServletRegistrationBean(new ViewStatusMessagesServlet(), "/lbClassicStatus");
    }
}

通过 http://host/yourWebapp/lbClassicStatus 可以访问到该界面

状态监听器 —— StatusListener

您可以将 StatusListener 注册到 StatusManager 上,这样您就可以立即对状态消息采取响应操作,特别是对 logback 配置之后发生的消息。注册状态监听器是一种不需要人工干预就可以监视 logback 内部状态的方便方法。

Logback 附带一个名为 OnConsoleStatusListener 的 StatusListener 实现。顾名思义,它将在控制台上打印所有传入的状态消息。

也可以在配置文件中注册一个或多个状态侦听器。

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  
  ... 
</configuration>

注意,注册的状态侦听器将仅在注册之后接收状态事件,因此最好将状态侦听器注册指令放在配置文件的顶部,然后再放置其他指令。

Logback 配置文件结构

Logback 配置文件的语法非常灵活。因此,不可能用 DTD 文件或 XML schema 指定允许的语法。但是,配置文件的最基本结构还是有的

如上,根节点为<configuration>元素,包含零个或多个<appender>元素,然后是零个或多个<logger>元素,然后是最多一个<root>元素

  • logger、root 作为日志的记录器,把它关联到应用的对应的 context 上后,主要用于存放日志对象,也可以定义日志类型、级别。
  • appender 主要用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、PostreSQL、 Oracle 和其他数据库、 JMS 和远程 UNIX Syslog 守护进程等。

<configuration>

<configuration debug="true" scan="true" scanPeriod="120 seconds">
  ...
</configuration>

诊断 Logback

如果你觉得 Logback 有问题,可以开启 Logback 诊断功能

开启方法以 xml 配置为例

<configuration debug="true">
  ...
</configuration>

或者

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  
  ...
</configuration>

这两种配置等同

修改后自动重新加载配置文件("热加载")

Logback 支持“热加载” ,即在项目运行过程中更改了配置会自动重新加载

开启方法以 xml 配置为例

<configuration scan="true">
  ...
</configuration>

默认情况下,每分钟扫描一次配置文件是否有更改。你也可以手动设置频率

<configuration scan="true" scanPeriod="120 seconds">
  ...
</configuration>

注意 scanPeriod 值的写法,数值英文空格时间单位 。 如果未指定时间单位,则默认时间单位为毫秒

由于在编辑配置文件时很容易出错,因此如果最新版本的配置文件具有XML语法错误,它将退回到没有XML语法错误的先前配置文件。

当你开启“热加载”后,Logback 会在后台启动一个 ReconfigureOnChangeTask 的 task,此 task 在单独的线程中运行,并按照设置的频率,检查您的配置文件是否已更改

变量 —— <property>

自定义变量

<configuration>
  <property name="APP_Name" value="myAppName" />
  ...
</configuration>

<configuration> 下,可以通过 <property> 来定义一个变量,属性 name 是变量的名称,属性 value 是变量的值

使用自定义变量

通过 ${变量名} 来引用变量

<configuration>
    <property name="LOG_PATTERN" value="%date %level [%thread] %logger{10} [%file : %line] %msg%n" />
    
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
  ...
</configuration>

日志输出器 —— <appender>

<appender> (日志输出器),用于将日志按照一定的格式输出到控制台、文件、数据库等地方,logger(日志记录器) 需要使用 appender(日志输出器) 将记录器中的日志输出!

appender 有两个必填属性

  • name :appender 的名称,任意填写,不要重名就行
  • calss :某个具体的 appender 的完全类名,它是 appender 的具体实现,Logback 自带常用的几个 appender。
    • ch.qos.logback.core.ConsoleAppender :将日志输出到控制台的 appender
    • ch.qos.logback.core.rolling.RollingFileAppender :将日志输出到文件,并按照条件切换输出到新建文件(滚动输出,自动切割)

编码器 —— <encoder>

<encoder> 负责将事件(日志)转换为字节数组,并将该字节数组写出为 OutputStream。

<encoder><appender> 的子节点,在 <encoder> 节点中,最重要的是配置 <pattern> ,它是用来格式化日志输出格式,

<appender name="" class="">
    <encoder>
        <pattern></pattern>
        <charset></charset> 
    </encoder>
</appender>

<pattern>

<pattern> 用于定义日志的输出格式,通过 Logback 中的转换说明符(Conversion specifier)(其实就是一些预定义变量),可以方便的组合出我们想要的日志格式

在这里我只列举出常用的转换说明符(Conversion specifier),更多请参考官方文档

日期、时间

假设系统时间是 2006-10-20 14:06:49,812

Conversion PatternResult
%d2006-10-20 14:06:49,812
%date2006-10-20 14:06:49,812
%date{ISO8601}2006-10-20 14:06:49,812
%date{HH:mm:ss.SSS}14:06:49.812
%date{yyyy-MM-dd HH:mm:ss.SSS, Asia/Shanghai}2006-10-20 14:06:49,812

日志记录器

Conversion PatternResult
%logger日志记录器的完整类名

类名、方法名、行号

Conversion PatternResult
%C、%class输出log的类的完整类名,获取类名的效率一般,只建议在需要时使用
%M 、%method输出log的方法
%L 、%line输出log的行号。获取行号的效率一般,只建议在需要时使用

日志信息

Conversion PatternResult
%m 、%msg 、%message日志信息

换行

Conversion PatternResult
%n换行

消息请求级别

Conversion PatternResult
%p、%le、 %level消息请求级别

线程

Conversion PatternResult
%t 、%thread输出日志的线程
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS, Asia/Shanghai} %red(%-5level) --- [%thread] %cyan(%class.%method/%line) : %msg%n</pattern>
    </encoder>
</appender>

颜色

对于控制台的输出,Logback 支持填充颜色,支持的颜色如下

  • %black()
  • %red()
  • %green()
  • %yellow()
  • %blue()
  • %magenta()
  • %cyan()
  • %white()
  • %gray()
  • %boldRed()
  • %boldGreen()
  • %boldYellow()
  • %boldBlue()
  • %boldMagenta()
  • %boldCyan()
  • %boldWhite()
  • %highlight()

使用方法是用颜色的代码把消息包起来,比如想将日志级别设置成红色 %red(%-5level)

设置字符集 —— <charset>

<charset> 是 <encoder> 的子节点,用于设置输出字符集

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS, Asia/Shanghai} %red(%-5level) --- [%thread] %cyan(%class.%method/%line) : %msg%n</pattern>
        <charset>UTF-8</charset> 
    </encoder>
</appender>

ConsoleAppender

ch.qos.logback.core.ConsoleAppender 是 Logback 自带的 appender,用于将日志输出到控制台。

对应控制台,只需要重点配置 <encoder> 节点

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %red(%-5level) --- [%thread] %cyan(%class.%method/%line) : %msg%n</pattern>
            <charset>UTF-8</charset> 
        </encoder>
    </appender>

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

RollingFileAppender

ch.qos.logback.core.rolling.RollingFileAppender 是 Logback 自带的 Appender,用于将日志输出到文件,并按照条件切换输出到新建文件(滚动输出,自动切割)

属性类型说明
fileString文件名,如果文件不存在,则创建。注意文件路径
appendbooleantrue 在文件末尾追加,false 覆盖,默认 true
encoderEncoder编码器,设置日志输出格式和编码
rollingPolicyRollingPolicy滚动策略,描述该如何滚动
triggeringPolicyTriggeringPolicy触发策略,什么条件下触发滚动

要使用 RollingFileAppender RollingPolicy (滚动策略)和 TriggeringPolicy(触发策略)必不可少。但是,如果 RollingPolicy 实现了 TriggeringPolicy 接口,则仅需要指定 RollingPolicy。

rollingPolicy(滚动策略) 里面有一个属性叫 fileNamePattern,它和 file 属性一样都表示日志文件名,具体区别下面会将

滚动策略 —— RollingPolicy

RollingPolicy 负责日志文件的移动和重命名的过程。
Logback 提供了多种 RollingPolicy 的实现

  • TimeBasedRollingPolicy :按时间滚动,比如按天或按月
  • SizeAndTimeBasedRollingPolicy :按时间和大小滚动,比如说在按天滚动的基础上设置大小,防止某天的日志文件过大!比如双11

TimeBasedRollingPolicy

ch.qos.logback.core.rolling.TimeBasedRollingPolicy 滚动策略同时也实现了 TriggeringPolicy(触发策略)

因此配置好 TimeBasedRollingPolicy 后,就不需要配置 TriggeringPolicy

TimeBasedRollingPolicy 有几个重要的属性

  • maxHistory :删除n个滚动周期之前的日志文件(最多保留前n个滚动周期的历史记录)。假设每分钟发送滚动,并将maxHistory设置为3,则每次发送滚动时,删除3分钟之前的归档文件。 举例:
    • 12:00 :归档文件0
    • 12:01 :归档文件1
    • 12:02 :无归档文件(比如这一分钟内没有产生log)
    • 12:03 :无归档文件(比如这一分钟内没有产生log)
    • 12:04 :触发滚动,删除3个滚动周期之前(也就是3分钟之前)的日志文件,因此,归档文件0 被删除,只剩下归档文件1

注意,由于删除了旧的归档日志文件,将适当删除为日志文件归档而创建的所有文件夹

  • totalSizeCap:在 maxHistory 限制的基础上,进一步限制 所有存档文件的总大小。当超过总大小上限时,将异步删除最旧的存档。totalSizeCap 依赖于 maxHistory ,如果没有 maxHistory ,单独设置 totalSizeCap 是不生效的

  • cleanHistoryOnStart:默认的删除操作是发送在触发滚动的过渡期间。但是,某些应用程序的生命周期可能不足以触发滚动,对于这种短暂的应用程序,归档删除可能永远都没有执行的机会。通过将 cleanHistoryOnStart 设置为 true,可以在 Appender 启动时执行归档删除。cleanHistoryOnStart 默认值为 false。

  • fileNamePattern 是最为复杂也最为重要的属性,同时也是必填项,单独拿出来说

fileNamePattern 属性

RollingFileAppender (TimeBasedRollingPolicy的上级)中的 file 属性可以设置也可以不设置。建议设置,因为通过设置包含 FileAppender 的 file 属性,可以解耦当前活动中日志文件的位置和归档日志文件的位置。

fileNamePattern 的值为 日志文件的路径名,支持绝对和相对路径和日期转换符——%d。

举例说明

  • 绝对路径: /Users/wqlm/work/project/test/logs/logback/application.log
  • 相对路径: logs/logback/application.log
  • 提醒:windows 环境注意目录分隔符

日期转换符——%d
%d 表示系统当前的时间,默认格式为 %d{yyyy-MM-dd},通过 %d{日期格式} 来自定义。

时区
%d 默认采用主机时区,也可以自定义%d{yyyy-MM-dd,UTC+8}

举例说明

  • 例1: logs/logback/%d/application.log
  • 例2: logs/logback/application_%d{yyyy-MM-dd,UTC+8}.log

除了以上作用,日期转换符还被用于推断触发滚动的时机!!!
比如,例1 会在每天凌晨触发滚动,创建一个新日期的文件夹和文件。例2 会在每天凌晨触发滚动,创建一个新日期的文件。

多个%d
使用多个%d,只有有一个用于推断触发滚动的时机,其余的必须加上 aux 参数来标记为非推断条件,例如 logs/logback/%d{yyyy-MM,aux}/application_%d.log

文件压缩 TimeBasedRollingPolicy 支持自动文件压缩。如果 fileNamePattern 属性值以.gz 或.zip结尾,则启用此功能。

情况1: <fileNamePattern>启动了压缩,但 appender 没有设置 <file>

<appender name="RFA" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--<file>logs/logback/application.log</file>-->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	<fileNamePattern>logs/logback/application_%d.zip</fileNamePattern>
        ...
    </rollingPolicy>
    ...
</appender>

由于没有设置 <file>,所以 logback 会根据 <fileNamePattern> 中内容生成一个叫 logs/logback/application_2019-12-31 的无后缀名的日志文件,并将当天日志写入该文件,到了第二天会将 logs/logback/application_2019-12-31 压缩成 logs/logback/application_2019-12-31.zip 并生成 logs/logback/application_2020-01-01 新日志文件

情况2: <fileNamePattern>启动了压缩, appender 也设置了 <file>

<appender name="RFA" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/logback/application.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	<fileNamePattern>logs/logback/application_%d.zip</fileNamePattern>
        ...
    </rollingPolicy>
    ...
</appender>

由于设置了 <file>,所以 logback 会将日志输出到 logs/logback/application.log ,到了第二天 logback 会根据 <fileNamePattern> 中的设置将 logs/logback/application.log 压缩成 logs/logback/application_2019-12-31.zip 并生成 logs/logback/application.log 新日志文件

触发滚动的时机 由于各种技术原因,滚动不是时钟驱动的,而是取决于日志事件的到来。

例如,在2002年3月8日,假设 fileNamePattern 设置为 yyyy-MM-dd (每日滚动),则在 2002-3-9 00:00:00 之后第一个事件的到来将触发滚动。

如果等到 2002-3-9 00:23:47 时,还没有收到日志事件,将不在继续等待,立即触发滚动

因此,根据事件的到达率,可能会有一定的延迟,但不会超过 00:23:47

总结 fileNamePattern 属性集多种功能于一身,包括

  • 设置动态文件名
  • 用于推断触发滚动的时机
  • 用于开启压缩策略
  • 影响 maxHistory 的单位(滚动周期)
  • 本身也描述了如何进行滚动

个人感觉 fileNamePattern 属性被设计的过“重”了

例子

<configuration>
    <appender name="TimeFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app1/app1.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>logs/app1/app1_%d.zip</fileNamePattern>
           <!--最多保留60个滚动周期的日志文件-->
           <maxHistory>60</maxHistory>
           <!--在 maxHistory 的前提下,所以日志文件总大小不能超过1GB-->
           <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date %-5level --- [%thread] %class.%method/%line : %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="DEBUG">
        <appender-ref ref="TimeFile" />
    </root>
</configuration>

SizeAndTimeBasedRollingPolicy

有时候,想在按照时间归档的基础上限制日志文件的大小,这时候就需要使用 SizeAndTimeBasedRollingPolicy。

比如按照每天归档的基础上,限制每个归档日志不能超过 50M ,防止像双11 这样的特殊日期,导致当日归档日志文件过大

SizeAndTimeBasedRollingPolicy 继承了 TimeBasedRollingPolicy ,因此 TimeBasedRollingPolicy 的功能它全都有,SizeAndTimeBasedRollingPolicy 仅比 TimeBasedRollingPolicy 多出一个 maxFileSize 属性

maxFileSize 属性表示单个归档日志文件的大小,单位有 KB、MB、GB

例子

<configuration>
    <appender name="SizeAndTimeFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app1/app1.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
          <fileNamePattern>logs/app1/backup/app1_%d_%i.zip</fileNamePattern>
           <!--单个日志文件最大10MB-->
           <maxFileSize>10MB</maxFileSize>
           <!--删除n个滚动周期之前的日志文件(最多保留前n个滚动周期的历史记录)件-->
           <maxHistory>60</maxHistory>
           <!--在 maxHistory 限制的基础上,进一步限制所有存档文件的总大小-->
           <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date %-5level --- [%thread] %class.%method/%line : %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="DEBUG">
        <appender-ref ref="SizeAndTimeFile" />
    </root>
</configuration>

%i 是一个滚动周期内的归档序列号,从0开始

注意:maxHistory 是表了 n 个滚动周期的日志文件,而不是 n 个的日志文件,在设置了 maxFileSize 后,一个滚动周期内可能有多个日志文件

日志记录器 —— <logger>

<logger name="" level="" additivity="" >
    <appender-ref ref="" />
</logger>

<logger> 有三个属性

  • name : 包名或类名。即,将该记录器作用于哪个类或包下。必填
  • level : 该记录器的级别,低于该级别的日志消息不记录。可选级别从小到大为 TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF(不区分大小写)。选填,不填则默认从父记录器继承level
  • additivity : 是否追加父 Logger 的输出源(appender),默认为true,选填。如果只想输出到自己的输出源(appender),需要设置为 false,参考例2

logger 还可以包含 0 或多个 <appender-ref> 元素, 将记录的日志使用 appender 进行输出

level 的继承

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.wqlm.boot.user.controller.UserController" ></logger>
    <logger name="com.wqlm.boot.user.controller" ></logger>

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

如上,name="com.wqlm.boot.user.controller.UserController"的 logger 没有定义 level,因此它会继承父 logger 的 level。

那它的父 logger 是谁呢? 其实就是下面的 name="com.wqlm.boot.user.controller"的 logger!

Logback 是根据包或类的层次结构来确定 logger 的父子关系的

继续往下说,name="com.wqlm.boot.user.controller"的 logger 也没有定义 level,因此它也会继承它的父 logger 的 level。但是,在该 xml 中,已经没有比它层次还“浅”的 logger 了,因此,它的父 logger 为 root。也就是说它会继承 root logger 的 level,也就是 info。

刚才是从上层往下层走,直到找到显示标注 level 的 logger。
现在要从下层往上层走,把继承到的 level 一层一层往上传,直到遇到显示标注 level 的 logger 才停止

因此,name="com.wqlm.boot.user.controller.UserController"的 logger 的 level 也是 info

练习 以下各 logger 的 level 是什么

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.wqlm.boot.user.controller.UserController" ></logger>
    <logger name="com.wqlm.boot.user.controller" level="warn"></logger>
    <logger name="com.wqlm.boot.user" ></logger>

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

按照上面讲的继承规则

name="com.wqlm.boot.user.controller.UserController"的 logger 的 level 是 warn
name="com.wqlm.boot.user"的 logger 的 level 是 info

additivity 的追加策略

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.wqlm.boot.user.controller.UserController" ></logger>
    <logger name="com.wqlm.boot.user.controller" ></logger>

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

如上,name="com.wqlm.boot.user.controller.UserController"的 logger 没有设置 additivity,因此 additivity 采用默认值 true,即,追加它父 logger 中的 appender 到自己的 logger 中。

在看它的父 logger —— name="com.wqlm.boot.user.controller" logger, additivity 也采用默认值,因此,它也会追加它父 logger 中的 appender 到自己的 logger 中。
在该 xml 中 name="com.wqlm.boot.user.controller" logger 的父 logger 为 root logger!,因此,它会追加 名为 STDOUT 的 appender 到自己的 logger 中

刚才是从上层往下层走,直到找到显示标注 appender="false" 的 logger 或 root logger 才停止追加。
现在要从下层往上层走,把层层追加的 appender 往上传,直到遇到显示标注 appender="false" 的 logger 才停止

因此,name="com.wqlm.boot.user.controller.UserController"的 logger 中,也有一个名为 STDOUT 的 appender,这个 appender 是从它的父 logger 中来的

所以,一般情况下,只需要给 root logger 添加 appender 即可!!!其他 logger 都会直接或间接获取到 root logger 的 appender

在看这种情况,每一个 logger 都有一个自己的 appender

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="STDOUT2" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    
    <appender name="STDOUT3" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.wqlm.boot.user.controller.UserController" additivity="true">
        <appender-ref ref="STDOUT3" />
    </logger>

    <logger name="com.wqlm.boot.user.controller">
        <appender-ref ref="STDOUT2" />
    </logger>

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

一个一个说 首先 name="com.wqlm.boot.user.controller" 的 additivity 没填,默认为 true,因此会追加 root logger 的 appender,这样一来,它就有两个 appender 的了,即 STDOUT 和 STDOUT2

同理,name="com.wqlm.boot.user.controller.UserController" 会追加 name="com.wqlm.boot.user.controller" 的 appender,因此,它有三个 appender,STDOUT 、 STDOUT2 、STDOUT3

练习,下面各 logger 的 appender 都有哪些?

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="STDOUT2" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    
    <appender name="STDOUT3" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.wqlm.boot.user.controller.UserController" additivity="true">
        <appender-ref ref="STDOUT3" />
    </logger>

    <logger name="com.wqlm.boot.user.controller" additivity="false">
        <appender-ref ref="STDOUT2" />
    </logger>

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

由于 name="com.wqlm.boot.user.controller" 的 logger 设置 additivity="false" 因此,它不追加父类的 appender,它的 appender 只有 STDOUT2

name="com.wqlm.boot.user.controller.UserController" 的 logger 的 appender 为 STDOUT2、STDOUT3

根记录器 —— <root>

<root level="">
    <appender-ref ref="" />
</root>

既然都叫 root 了,自然是不需要 name 属性,然后因为是根记录器,也不需要 additivity 属性,只有 level 是必须填写的

level 的可选值有 TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF,大小写都可以识别

root 也可以包含 0 或多个 <appender-ref> 元素