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这么受欢迎的原因–我们可以很快搭建上手。
从以上配置文件以及代码可以推断出其工作方式:
- 配置了一个取名为STDOUT引用ConsoleAppender类的appender
- 指定了日志消息输出格式的模板
- 在代码中创建一个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对象继承体系相似的继承关系:
- 当日志对象的名字,即getLogger()中的内容,如果后面有一个日志对象取名以改名字后面加一个点(.)加上其他内容的,那么签名的称为祖先,后面的为继承者,如果继承者未设置日志级别,那么其就是祖先的日志级别
- 如果一个日志对象在其和其子日志对象都没有祖先日志对象,那么其就是父日志对象。
** 所有的日志对象都是根日志对象的继承者。 **
一个日志对象有一个日志级别,该级别可以通过配置文件设置或者使用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配置文件的读取规则:
- 在classpath下依次(依旧是优先级由大到小)搜索配置文件logback-test.xml、logback.groovy或者logback.xml。
- 如果logback库没有找到1中的文件,将会尝试使用java的ServiceLoader去定位com.qos.logback.classic.spi.Configurator的实现器(implementor)。
- 配置直接打印日志到控制台
注意:当前的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><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