配置文件的读取顺序

  1. logback 会在类路径下寻找名为 logback-test.xml 的文件。
  2. 如果没有找到,logback 会继续寻找名为 logback.groovy 的文件。
  3. 如果没有找到,logback 会继续寻找名为 logback.xml 的文件。
  4. 如果没有找到,将会通过 JDK 提供的 ServiceLoader 工具在类路径下寻找文件 META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了 Configurator 接口的实现类的全限定类名。
  5. 如果以上都没有成功,logback 会通过 BasicConfigurator 为自己进行配置,并且日志将会全部在控制台打印出来。

最后一步的目的是为了保证在所有的配置文件都没有被找到的情况下,提供一个默认的(但是是非常基础的)配置。我们来看一个官方示例,当没有配置相关的配置文件的时候

public class MyApp1 {
    public static final Logger LOGGER = LoggerFactory.getLogger(MyApp1.class);

    public static void main(String[] args) {
        LOGGER.info("Entering application.");

        Foo foo = new Foo();
        foo.doIt();
        LOGGER.info("Exiting application.");
    }
}

//Foo类    
    public class Foo {
    public static final Logger LOGGER = LoggerFactory.getLogger(Foo.class);

    public void doIt() {
        LOGGER.debug("Did it again!");
    }
}

输出:

22:37:22.783 [main] INFO com.exercise.MyApp1 - Entering application.
22:37:22.787 [main] DEBUG com.exercise.Foo - Did it again!
22:37:22.787 [main] INFO com.exercise.MyApp1 - Exiting application.

现在我们要使用配置文件来代替默认的控制台输出,logback.xml文件由如下三部分组成

2
2

配置文件大致组成如下

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

我们从最简单的开始:

我们在resources目录下新建一个logback-test.xml 或 logback.xml文件

内容:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
        <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>

运行 MyApp1,你会发现输出的结果和上面的默认无二差别。

如果在解析配置文件的过程当中发生了错误,logback 会在控制台打印出它的内部状态数据。如果用户明确的定义了状态监听器,为了避免重复,logback 将不会自动打印状态信息。

在没有警告或错误的情况下,如果你想查看 logback 内部的状态信息,可以通过 StatusPrinter 类来调用 print() 方法查看具体的信息。

我们通过增加几行代码来验证一下StatusPrinter.print()方法

public class MyApp2 {
    public static final Logger LOGGER = LoggerFactory.getLogger(MyApp2.class);

    public static void main(String[] args) {

        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        StatusPrinter.print(lc);

        LOGGER.info("Entering application.");

        Foo foo = new Foo();
        foo.doIt();
        LOGGER.info("Exiting application.");
    }
}

输出:

22:43:00,701 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
22:43:00,701 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
22:43:00,702 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/D:/project/log-exercise/target/classes/logback.xml]
22:43:00,770 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
22:43:00,771 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
22:43:00,778 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
22:43:00,783 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
22:43:00,829 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
22:43:00,829 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
22:43:00,830 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
22:43:00,831 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@57f23557 - Registering current configuration as safe fallback point

22:43:00.835 [main] INFO  com.exercise.MyApp2 - Entering application.
22:43:00.838 [main] DEBUG com.exercise.Foo - Did it again!
22:43:00.838 [main] INFO  com.exercise.MyApp2 - Exiting application.

这时候我们可以清楚看到logback.xml文件被找到且被读取,appender被应用,以及有效的日志等级等信息被打印出来。当然我们还有其他方式打印出这些logback的内部状态信息,即在configuration 标签上加上debug="true"

如下:

<configuration debug="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <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>

这种方式也有利有弊,如果配置文件的配置有问题,logback 会检测到这个错误并且在控制台打印它的内部状态。但是,如果配置文件没有被找到,logback 不会打印它的内部状态信息,因为没有检测到错误。通过编码方式调用 StatusPrinter.print() 方法会在任何情况下都打印状态信息。

强制输出状态信息:在缺乏状态信息的情况下,要找一个有问题的配置文件很难,特别是在生产环境下。为了能够更好的定位到有问题的配置文件,可以通过系统属性 "logback.statusListenerClass" 来设置 StatusListener 强制输出状态信息。系统属性 "logback.statusListenerClass" 也可以用来在遇到错误的情况下进行输出。

设置 debug="true" 完全等同于配置一个 OnConsoleStatusListener

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  
。。。。其他配置 
</configuration>

配置文件更改自动加载

为了让 logback 能够在配置文件改变的时候自动去扫描,需要在 <configuration> 标签上添加 scan=true 属性。

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

默认情况下,一分钟扫描一次配置文件,看是否有更改。通过标签上的 scanPeriod 属性可以指定扫描周期。扫描周期的时间单位可以是毫秒、秒、分钟或者小时。

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

注意:如果没有指定时间单位,则默认为毫秒。

当设置了 scan="true",会新建一个 ReconfigureOnChangeTask 任务用于监视配置文件是否变化。ReconfigureOnChangeTask 也会自动监视外部文件的变化。

如果更改后的配置文件有语法错误,则会回退到之前的配置文件。

在堆栈中展示包数据

注意:在 1.1.4 版本中,展示包数据是默认被禁用的
如果启用了展示包数据,logback 会在堆栈的每一行显示 jar 包的名字以及 jar 的版本号。展示包数据可以很好的解决 jar 版本冲突的问题。但是,这个的代价比较高,特别是在频繁报错的情况下。
启用展示包数据:

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

配置 logger

在上面我们知道了日志是有分等级的,所以这里的配置就是在做日志输出的有效等级
通过 <logger> 标签来过 logger 进行配置,一个 <logger> 标签必须包含一个 name 属性,一个可选的 level 属性,一个可选 additivity 属性。additivity 的值为 true 或 false。level 的值为 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,INHERITED,NULL。当 level 的值为 INHERITED 或 NULL 时,将会强制 logger 继承上一层的级别。

<logger> 元素至少包含 0 或多个 <appender-ref> 元素。每一个 appender 通过这种方式被添加到 logger 上。与 log4j 不同的是,logbakc-classic 不会关闭或移除任何之前在 logger 上定义好的的 appender。

配置 root logger

root logger 通过 <root> 元素来进行配置。它只支持一个属性——level。它不允许设置其它任何的属性,因为 additivity 并不适用 root logger。而且,root logger 的名字已经被命名为 "ROOT",也就是说也不支持 name 属性。level 属性的值可以为:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF,但是不能设置为 INHERITED 或 NULL。

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

综合上面的logger,我们看个例子,假定logback配置文件如下:

<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

    <logger name="com.exercise" level="info">

    </logger>
    
</configuration>

测试代码:

package com.exercise.newpac;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author chenly
 * @create 2019-12-30 21:32
 */
public class TestAppender {
    public static final Logger LOGGER = LoggerFactory.getLogger(TestAppender.class);

    public void doIt() {
        LOGGER.debug("Did it again!");

        LOGGER.error("Did it error!");

        LOGGER.warn("Did it warn!");
    }
}
package com.exercise;

import com.exercise.newpac.TestAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author chenly
 * @create 2020-01-02 20:06
 */
public class MyApp3 {
    public static final Logger LOGGER = LoggerFactory.getLogger(MyApp2.class);

    public static void main(String[] args) {
        
        LOGGER.info("Entering application.");
        
        TestAppender testAppender=new TestAppender();
        testAppender.doIt();
        
        LOGGER.info("Exiting application.");
    }
}

输出:

20:07:36.953 [main] INFO  com.exercise.MyApp2 - Entering application.
20:07:36.955 [main] ERROR com.exercise.newpac.TestAppender - Did it error!
20:07:36.956 [main] WARN  com.exercise.newpac.TestAppender - Did it warn!
20:07:36.956 [main] INFO  com.exercise.MyApp2 - Exiting application.

这个例子验证了,日志打印如果logger没有找到appender会向上寻找使用父级,直到ROOT logger,而且使用了父级的appender他也不会受到父级的logger的有效日志等级的影响,它只依赖被调用 logger 的有效日志级别

假如我们在当前logger也加了appender会怎么样呢?我们实验一下:

<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

    <logger name="com.exercise" level="info">
        <appender-ref ref="STDOUT"/>
    </logger>

</configuration>

输出:

22:00:04.253 [main] INFO  com.exercise.MyApp2 - Entering application.
22:00:04.253 [main] INFO  com.exercise.MyApp2 - Entering application.
22:00:04.256 [main] ERROR com.exercise.newpac.TestAppender - Did it error!
22:00:04.256 [main] ERROR com.exercise.newpac.TestAppender - Did it error!
22:00:04.256 [main] WARN  com.exercise.newpac.TestAppender - Did it warn!
22:00:04.256 [main] WARN  com.exercise.newpac.TestAppender - Did it warn!
22:00:04.256 [main] INFO  com.exercise.MyApp2 - Exiting application.
22:00:04.256 [main] INFO  com.exercise.MyApp2 - Exiting application.

你会发现打印了两次,没错,从当前Logger开始,通过每个logger对象的parent属性依次向上游历所有的相关logger,直到root logger,每次遍历都会调用appender的打印方法。

关于这个原因查看下一篇文章的源码解析:Logback(4) 从日志打印两次看日志打印流程

配置 appender

appender 通过 <appender> 元素进行配置,两个必要的属性: name 与 class。name 属性用来指定 appender 的名字,class 属性需要指定类的全限定名用于实例化。<appender> 元素可以包含 0 或一个 <layout> 元素,0 或多个 <encoder> 元素,0 或多个 <filter> 元素。除了这些公共的元素之外,<appender> 元素可以包含任意与 appender 类的 JavaBean 属性相一致的元素。
注:encoder 是logback从 0.9.19 版本之后引入的,替代原有的layout

Appender Syntax
Appender Syntax

<layout> 元素必须要通过 class 属性去指定一个类的全限定名,用于实例化。与 <appender> 元素一样,<layout> 元素也可以包含与 layout 实例相关的属性。如果 layout 的 class 是 PatternLayout,那么 class 属性可以被隐藏掉(参考:默认类映射)。

<encoder> 元素也必须要通过 class 属性去指定一个类的全限定名,用于实例化。如果 encoder 的 class 是 PatternLayoutEncoder,那么基于默认类映射(同上),class 属性可以被隐藏。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>myApp.log</file>
        <encoder>
            <pattern>
                %date %level [%thread] %logger{10} [%file:%line] %msg%n
            </pattern>
        </encoder>
    </appender>

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

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

java类文件:

package com.exercise.appenderconfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;

/**
 * @author chenly
 * @create 2020-01-04 12:02
 */
public class MyApp {
    public static final Logger LOGGER = LoggerFactory.getLogger(MyApp.class);

    public static void main(String[] args) {
        LOGGER.info("hello,now is {}", LocalDateTime.now());
    }
}

输出:

  1. 控制台输出了:hello,now is 2020-01-04T12:03:37.138
  2. 输出文件 myApp.log:内容

    2020-01-04 12:03:37,140 INFO [main] c.e.a.MyApp [MyApp.java:16] hello,now is 2020-01-04T12:03:37.138

设置 context

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录。

在logback配置文件中加入这个上下文字段,那么日志打印的时候就会一起被打印出来

<configuration>
    <contextName>myAppName</contextName>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

输出:

2020-01-05 11:31:44,061 myAppName [main] INFO com.exercise.appenderconfig.MyApp - hello,now is 2020-01-05T11:31:44.061

变量的定义

logback 支持变量的定义以及替换,变量有它的作用域。而且,变量可以在配置文件中,外部文件中,外部资源文件中,甚至动态定义。

从简单的来看,配置propertynamevalue;其中name的值是变量的名称,value的值时变量定义的值。通过property定义的值会被插入到logger上下文中。定义变量后,可以使“${name}”来使用变量。

例如:

....skip
<property name="USER_NAME" value="/data/logs" />

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${USER_NAME}/myApp.log</file>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
...skip

一般来说我们可以通过这种方式来定义我们的日志输出路径。当然如果我们通过java -D的方式也可以指定一个系统变量,达到效果是一样的。

property的功能也不仅仅局限于此,它还可以读取文件。如果你的变量很多,那么出于整洁性,我们会将这些变量统一放置到一个文件里,通过下面的例子就可以读取到配置文件里的内容:

....skip
<property file="F:\project\logback-examples\src\main\resources\variables1.properties"/>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${USER_HOME}/myApp.log</file>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
...skip
//variables1.properties 文件内容
USER_HOME=/data/logs

也可以引用 classpath 下的资源文件,不用写绝对路径:

<property resource="resource1.properties" />

property 作用域

属性的作用域分别为本地(local scope)、上下文(context scope)、系统(system scope)。默认为本地作用域。

本地(local scope):本地范围内的属性存在配置文件的加载过程中。配置文件每加载一次,变量就会被重新定义一次。

上下文(context scope):上下文范围内的属性会一直存在直到上下文被清除。

系统(system scope):系统范围内的属性,会插入到 JVM 的系统属性中,跟随 JVM 一同消亡。

在进行变量替换的时候,会先从本地范围去找,再从上下文去找,再从系统属性中去找,最后会去系统的环境变量中去找。

引入文件

通过 <include> 元素可以引入外部的配置文件。

例如:

<configuration>
    <include file="src/main/resources/includedConfig.xml" />

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

目标文件必须是由 <included> 元素包裹的。

<included>
    <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d - %m%n</pattern>
        </encoder>
    </appender>
</included>

可以通过如下几个属性引入文件:

  • 通过文件引入:

可以通过 file 属性引入外部文件。可以通过相对路径或者绝对路径来引入。相对路径是指相对应用程序的路径。

  • 通过资源文件引入

可以通过 resource 属性来引入位于 classpath 路径下的资源文件。

<include resource="includedConfig.xml"/>
  • 通过 url 引入文件

可以通过 url 属性来引入外部文件。

<include url="http://some.host.com/includedConfig.xml"/>

如果 logback 没有通过 include 元素找到指定的配置文件,会在控制台打印出内部状态信息。如果引入的外部配置文件是可选的,可以设置 optional=true。

<include optional="true" ..../>