logback的使用与配置教程

logback指南(A Guide To Logback)

1. 总体介绍(Overview)

logback是java社区使用最广泛的日志框架之一。它是Log4j的继任者。相对于Log4j,logback实现了更好的性能、提供了更多的配置选项以及更灵活的就日志文件归档。
本文将介绍logback的架构,帮助你在应用中更好的使用它。

2. Logback架构(Logback Architecture)

logback主要由三部分组成:Logger、Appender、以及Layout。

  • logger是打印日志消息的内容。该类主要是应用程序创建用于打印日志的对象。即创建该对象,使用该对象用于打印我们需要的日志。
  • appenders 设置了日志消息最终的目的地,即具体的日志内容输出的地方。一个日志对象(logger)可以有多个appender。我们通常想到的就是将appender设置为文本文件(即日志内容存放到文件中),但是远不止这些(比如设置为直接输出到控制台)。
  • layout准备输出的消息(译者注:也就是格式化输出的日志消息内容,比如时间,类,具体的消息等)。logback支持创建格式化消息的传统类,也支持根据已经存在的进行模糊配置。

3. 设置

3.1 maven依赖

logback使用java简单日志门面模式(Simple Logging Facade for Java)(SLF4J)作为其实现接口。在我们使用logback之前,需要在pom.xml中添加需要的依赖。

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
		<!--注,原文中使用的是1.8.0-beta2版本,目前看没有1.8.0的正式版,而使用该beta版无法识别logback.xml配置文件,无法使用,所以使用的是最新的1.7.x版本-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

想使用更新的版本可以去maven中央仓库中去查询是否有最新的版本。

4. 基本例子和配置(Basic Example and Configuration)

让我们在应用中快速搭建使用一个例子。
首先,需要创建一个取名为logback.xml的配置文件,直接放到classpath路径下。

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

然后创建一个main类:

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

/**
 * @author hexin
 * @version 1.0 2018/11/13
 */
public class Test {
    private final static Logger logger = LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        logger.info("Example log from {}", Test.class.getSimpleName());
    }
}

//output:
16:48:39.319 [main] INFO  com.hexin.learning.logback.Test -Example log from Test

以上就运行成功了,这也是为什么logback这么受欢迎的原因–我们可以很快搭建上手。
从以上配置文件以及代码可以推断出其工作方式:

  1. 配置了一个取名为STDOUT引用ConsoleAppender类的appender
  2. 指定了日志消息输出格式的模板
  3. 在代码中创建一个Logger对象,并且通过info()方法输出消息。

5. 日志对象(Logger Context)

5.1 创建一个日志对象

要使用logback打印日志,首先需要通过SLF4J或者Logback创建一个日志对象

 private final static Logger logger = LoggerFactory.getLogger(Test.class);

然后使用:

logger.info("Example log from {}", Test.class.getSimpleName());

这就是日志对象,我们通过工厂类LoggerFactory创建,传入的类提供了日子的名字(也提供了一个直接提供字符串的重载方法)。
日志对象存在和java对象继承体系相似的继承关系:

  1. 当日志对象的名字,即getLogger()中的内容,如果后面有一个日志对象取名以改名字后面加一个点(.)加上其他内容的,那么签名的称为祖先,后面的为继承者,如果继承者未设置日志级别,那么其就是祖先的日志级别
  2. 如果一个日志对象在其和其子日志对象都没有祖先日志对象,那么其就是父日志对象。

所有的日志对象都是根日志对象的继承者。
一个日志对象有一个日志级别,该级别可以通过配置文件设置或者使用Logger.setLevel()方法在代码中覆写配置文件。
日志级别从小到大一次为:TRACE,DEBUG,INFO,WARN,以及ERROR,每个级别都有相应的方法输出该级别的日志信息。
如果一个日志对象未指定日志级别,从最近的祖先处继承。 根日志对象默认级别为DEBUG。

5.2 使用日志对象

通过一个实际例子理解上面的描述:

	//继承关系例子
    private static void testParentHierarchy() {
        //注意该Logger是ch.qos.logback.classic.Logger, 因为slf4j中的logger对象是没有setLevel方法的,以下都是该对象
        Logger parentLogger =
                (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");
        parentLogger.setLevel(Level.INFO);

        //根据取名规则,该日志对象是.以前的是其符日志对象,所以该初始化的日志级别也是INFO
        Logger childLogger =
                (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback.tests");

        parentLogger.warn("This message is logged because WARN > INFO");
        //设置了info级别的日志,所以debug无法打印出
        parentLogger.debug("This message is not logged because DEBUG < INFO");
        childLogger.info("INFO == INFO");
        //该日志对象继承了父类的,所以debug级别的日志无法打印
        childLogger.debug("DEBUG < INFO");
    }
//output:
17:21:46.703 [main] WARN  com.baeldung.logback -This message is logged because WARN > INFO
17:21:46.707 [main] INFO  com.baeldung.logback.tests -INFO == INFO
	//root日志对象例子
    private static void testRootHierarchy() {
        Logger logger =
                (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");
        logger.debug("Hi there!");
        //rootLogger默认的日志级别是debug
        Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

        rootLogger.debug("This message is logged because DEBUG == DEBUG");

        rootLogger.setLevel(Level.ERROR);
        //已经将日志级别改为error,所以warn级别的不会被打印出来
        rootLogger.warn("This message is not logged because WARN < ERROR");
        rootLogger.error("This is logged");
    }
//output:
17:23:47.136 [main] DEBUG com.baeldung.logback -Hi there!
17:23:47.140 [main] DEBUG ROOT -This message is logged because DEBUG == DEBUG
17:23:47.140 [main] ERROR ROOT -This is logged

5.3 参数化消息

通过例子来说明:

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

/**
 * @author hexin
 * @version 1.0 2018/11/13
 */
public class ParameterizeMessage {
    private final static Logger logger = LoggerFactory.getLogger(ParameterizeMessage.class);

    public static void main(String[] args) {
        String message = "This is a string";
        Integer zero = 0;
        try {
            logger.debug("Logging message: {}", message);
            logger.debug("Going to divide {} by {}", 42, zero);
            int result = 42 / zero;
        } catch (Exception e) {
			//对于异常对象e,没有使用大括号占位符
            logger.error("Error dividing {} by {}", 42, zero, e);
        }
    }
}
//output:
17:26:37.152 [main] DEBUG c.h.l.logback.ParameterizeMessage -Logging message: This is a string
17:26:37.157 [main] DEBUG c.h.l.logback.ParameterizeMessage -Going to divide 42 by 0
17:26:37.158 [main] ERROR c.h.l.logback.ParameterizeMessage -Error dividing 42 by 0
java.lang.ArithmeticException: / by zero
	at com.hexin.learning.logback.ParameterizeMessage.main(ParameterizeMessage.java:24)

不需要直接通过log.debug("Current count is " + count);这种字符串的拼接方式,logback支持消息参数化,即通过大括号{}提供一个占位符,直接通过参数的形式将各种格式的内容传入进去,对于Object对象,会调用该对象的toString()方法填充占位符,同时对于异常信息,也可以直接打印出堆栈信息。

6. 详细配置(Detailed Configuration)

在前面第4部分的例子中,我们使用创建了一个11行的配置文件打印日志到控制台。这个是logback的默认行为:如果不能够找到配置文件,将创建一个与根日志(root logger)对象相关的控制台输出对象(ConsoleAppender)。

6.1 定位配置信息

配置文件可以放在classpath下并且取名为logback.xml或者logback-test.xml。
以下是logback配置文件的读取规则:

  1. 在classpath下依次(依旧是优先级由大到小)搜索配置文件logback-test.xml、logback.groovy或者logback.xml。
  2. 如果logback库没有找到1中的文件,将会尝试使用java的ServiceLoader去定位com.qos.logback.classic.spi.Configurator的实现器(implementor)。
  3. 配置直接打印日志到控制台

注意:当前的logback版本不支持groovy配置,因为目前还没有兼容java9的groovy

6.2 基本配置

让我们再次详细看下例子中的配置。
整个配置内容都在标签中。

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

以上标签中,定义了一个名叫STDOUT的ConsoleAppender类型的appender。内部有一个encoder标签,看起来像printf风格的占位符模板代码。

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

以上是root标签,该标签设置了root日志为DEBUG模式,并将其关联到名为SDTOUT的appender中。

6.3 配置文件常见问题

logback配置文件可能变得很复杂,因此内建了一些问题解决机制。
可以通过开启debug日志打印模式可以观察logback解析配置的debug信息。

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

logback在解析配置信息时会打印状态信息到控制台上:

23:54:23,040 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml] at [file:/Users/egoebelbecker/ideaProjects/logback-guide/out/test/resources/logback-test.xml]
23:54:23,230 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
23:54:23,236 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
23:54:23,247 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
23:54:23,308 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
23:54:23,309 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
23:54:23,310 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
23:54:23,313 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@5afa04c - Registering current configuration as safe fallback point

如果解析配置文件发生警告(warnings)或者错误(errors)时,logback会将状态信息打印到控制台上。
第二种打印状态信息的方式如下:

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

StatusListener会拦截状态信息,会在解析配置文件以及程序运行的时候打印信息。
打印所有的配置文件信息使得很容易定位到“流氓”配置文件。

6.4 自动重新加载配置

在程序运行中重新加载配置是一种很强的问题解决工具。通过以下配置可以实现该功能:

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

默认的自动扫描配置文件的间隔时间是60秒,可以指定scanPeriod的值手动设置扫描间隔时间,如下:

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

可指定的时间有milliseconds,seconds,minutes或者hours。

6.5 修改loggers

在第4部分中,我们设置了root logger的级别,同时将其关联到了console appender上。
我们可以对任何logger设置级别:

<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>
    <logger name="com.baeldung.logback" level="INFO" /> 
    <logger name="com.baeldung.logback.tests" level="WARN" /> 
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

添加到classpath并运行如下代码:

Logger foobar = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.foobar");
Logger logger = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");
Logger testslogger = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback.tests");
 
foobar.debug("This is logged from foobar");
logger.debug("This is not logged from logger");
logger.info("This is logged from logger");
testslogger.info("This is not logged from tests");
testslogger.warn("This is logged from tests");

//output:
00:29:51.787 [main] DEBUG com.baeldung.foobar - This is logged from foobar
00:29:51.789 [main] INFO com.baeldung.logback - This is logged from logger
00:29:51.789 [main] WARN com.baeldung.logback.tests - This is logged from tests

没有在配置文件中指定的logger,比如上面的com.baeldung.foobar会直接继承root logger指定的级别。
logger也从root logger中继承appender-ref。

6.6 变量替换

logback配置支持定义变量。可以在配置脚本中或者外部定义变量。
变量可以在配置脚本中的任意一个位置代表一个值。
例如,下面是一个FileAppender的配置:

<property name="LOG_DIR" value="/var/log/application" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_DIR}/tests.log</file>
    <append>true</append>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

在配置的顶部,我们定义了一个LOG_DIR属性,然后我们将其作为配置路径的一部分。
属性在标签中定义,但是在外部资源中也是有效的,比如系统属性。也可以在命令行中指定属性:

$ java -DLOG_DIR=/var/log/application com.baeldung.logback.LogbackTests

我们通过使用${propertyname}使用定义的属性值,logback会识别该变量占位符并使用文本替换到指定的部分,在配置脚本中的任意位置都可以识别。

7. Appenders

logger会将日志事件(LoggingEvents)到appenders,appenders会处理真正的日志工作。日志通常被写入文件或者在控制台中打印出来,但是logback可以做更多。logback-core提供了几个有用的appenders。

7.1 ConsoleAppender

第4部分中,已经见过该appender了,正如名字那样说的,ConsoleAppender将信息写入System.out或者Sytem.err中。
使用OuputStreamWriter缓存到I/O,因此直接导引到System.err不会导致非缓存写。

7.2 FileAppender

FileAppender将日志信息写入到文件中。支持一些列配置参数。以下是一个配置例子:

<configuration debug="true">
    <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>
 
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>tests.log</file>
        <append>true</append>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
     
    <logger name="com.baeldung.logback" level="INFO" /> 
    <logger name="com.baeldung.logback.tests" level="WARN"> 
        <appender-ref ref="FILE" /> 
    </logger> 
 
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

FileAppender通过标签配置文件名。标签指定了是使用追加日志的方式而不是删除重建。如果运行几次程序,我们就可以发现是在同一个日志文件追加写入的。
如果使用上面的配置文件重新运行一次,我们会发现com.baeldung.lgoback.test的日志会同时打印在控制台以及写入test.log日志文件中。也就是说子类logger会继承root logger关联的配置。Appenders的继承会累积的(也就是说可以往子类一直传)。
可以通过配置additivity为false禁用默认行为,就不会继承root logger的配置了,同时其子类也不会继承root logger。

<logger name="com.baeldung.logback.tests" level="WARN" additivity="false" > 
    <appender-ref ref="FILE" /> 
</logger> 
 
<root level="debug">
    <appender-ref ref="STDOUT" />
</root>

7.3 RollingFileAppender

将日志追加到同一个日志文件经常不是我们想要的。我们希望根据时间、日志文件大小或者两者结合来“滚动”文件。

<property name="LOG_FILE" value="LogFile" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_FILE}.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- daily rollover -->
        <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
 
        <!-- keep 30 days' worth of history capped at 3GB total size -->
        <maxHistory>30</maxHistory>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender> 

RollFileAppender有一个RollgingPolicy(滚动策略)。在上面的例子中,我们使用了TimeBasedRollingPoligy,和FileAppender详细,我们配置了一个文件名,在这里又定义了一个占位属性,因为可以复用这个名字。
在RollingPolicy中定义了fileNamePattern,该模板不仅仅是定义了文件名,同时也指定了滚动策略。TimeBasedRollingPolicy会检查该模板,在配置的回滚模板条件下进行文件的滚动。
例如:

<property name="LOG_FILE" value="LogFile" />
<property name="LOG_DIR" value="/var/logs/application" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/${LOG_FILE}.log</file> 
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOG_DIR}/%d{yyyy/MM}/${LOG_FILE}.gz</fileNamePattern>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>

当前活跃的文件是/var/logs/application/LogFile(也就是日志正写入的文件)。该文件会在每个月的开始进行滚动操作,将其压缩为``/Current Year/Current Month/LogFile.gz,同时RollingFileAppender会创建一个新的活跃文件用于当前以后的日志写入。
当总的日志归档文件到达3GB时,RollingFileAppender会删除最早的哪一个滚动的文件。RollingFileAppender也支持内建的文件压缩功能,当我们指定的归档文件类型的后缀为压缩格式时,会自动对滚动日志文件进行压缩,比如上面的LogFile.gz。
TimeBasedPolicy不是我们唯一的滚动类型。Logback也提供了SizeAndTimeBasedPolicy,根据日志文件大小以及时间进行日志滚动操作。也有FixedWindowRollingPolicy,每次日志启动时会进行滚动。
也可以自行实现RollingPolicy。

7.4 定制Appenders

可以通过继承logback的基础appender类中的一个实现定制化的appenders,可以查阅相关文档。

8. layouts

layout负责日志消息的格式化。像logback的其他部分一样,layouts也是可扩展的。
默认的PatternLayout提供了大部分需要的格式。
如下是一个例子:

<encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>

该配置脚本包含了PatternLayoutEncoder的配置,传递encoder给appender,encoder使用PatternLayout对消息进行格式化。
标签中的内容定义了消息的格式化模板。PatternLayout实现了非常多的约定的格式与创建模板的修饰符。
以以上例子为例,PatternLayout通过一个%识别约定格式字符:

  • %d{HH:mm:ss.SSS}——指定了小时、分钟、秒以及毫秒的时间
  • [%thread]——输出日志的线程名,已方括号包围
  • %-5level——日志时间的输出级别,填充到5个字符
  • %logger{36}——logger的名字,缩减到35个字符
  • %msg%n——日志的信息后面跟上对应系统的换行符

日志输出示例:

21:32:10.311 [main] DEBUG com.baeldung.logback.LogbackTests - Logging message: This is a String

其他的格式化模板符号参看:https://logback.qos.ch/manual/layouts.html#conversionWord

原文地址:https://www.baeldung.com/logback

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章