23 KiB
Logback
Introduce
logback-classic模块需要classpath下存在slf4j-api.jar, logback-core.jar, logback-classic.jar。
logback使用示例如下:
package chapters.introduction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld1 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
logger.debug("Hello world.");
}
}
HelloWorld1类定义在chapters.introduction包下,其引入了slf4j中的Logger和LoggerFactory类。
在上述示例中,并没有引入任何logback的类,在使用日志的大多数场景中,只需要引入slf4j的api类即可。
Logback Architecture
logback目前结构分为3个模块,logback-core,logback-classic,logback-access。
logback-core是其他两个模块的基础,classic模块拓展了core模块。classic模块实现了slf4j api,因而在使用slf4j时可以在logback和其他日志系统之间轻松切换。而access模块和servlet容器做了集成,可以通过http对日志进行访问。
Logger, Appender, Layout
logback基于3个主要的类构成:Logger, Appender, Layout。Logger类属于logback-classic模块,Appender和Layout类则是属于logback-core模块。
Log Context
在logback-classic中,每个logger都关联了一个Log Context,log Context负责创建logger,并且log context将创建的logger按照树状结构排列。
Logger都拥有名称,其名称大小写敏感,并遵循分层的命名规则:
如果一个logger的名称,其名称加上
.后形成的字符串,为另一个logger的前缀,那么可称后一个logger是前一个logger的后代。如果一个logger和后代logger之间没有其他的logger,那么可以称前一个logger是后一个logger的parent。
例如,名称为"com.foo"的logger是名称为"com.foo.Bar"的parent。
root logger是位于最顶层的logger,其可以通过如下方式进行获取:
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
其他所有的logger也可以通过getLogger方法进行获取,该方法接收logger name来作为参数,Logger接口具有如下方法:
package org.slf4j;
public interface Logger {
// Printing methods:
public void trace(String message);
public void debug(String message);
public void info(String message);
public void warn(String message);
public void error(String message);
}
有效级别
logger都会被指定一个log level,level可以是TRACE, DEBUG, INFO, WARN, ERROR中的一个,其定义在ch.qos.logback.classic.Level类中。如果指定logger没有被指定level,那么其会从离其最近并且被指定level的先祖logger中继承level。
为了保证所有logger都可以继承level,root logger一定会被指定log level。默认情况下,level为debug。
log level顺序为:
TRACE < DEBUG < INFO < WARN < ERROR
获取Logger
在通过LoggerFactory.getLogger获取logger对象时,如果向方法传入相同的名称,那么会获得相同的logger对象。
通常,应在每个class中指定一个logger,logger name为class的全类名。
Appender
在logback中,允许将日志打印到多个目的地,在logback中,目的地被称为Appender。目前,目的地可以是console、文件、remote socket server、数据库、JMS等。
对每个logger,都可以关联不止一个Appender。
addAppender将会将Appender关联到指定的logger。每个被允许打印的log请求都会被发送到该logger关联的所有Appender中,并且log请求还会被发送到树结构更高的Appender中。在logger结构中,Appender是增量继承的,后代logger会继承先祖logger的Appender。
如果为root logger指定一个console appender,并且为L指定一个file appender,那么对于L和L的后代logger打印日志的请求不仅会被发送到console,还会被打印到文件中。
可以手动指定logger L增量继承appender的标志为false,那么L和其所有后代logger都不会继承L先祖logger的appender。
默认情况下,appender是否增量继承的标志,默认值为true
Layout
如果想要指定日志打印的格式,可以将Layout对象和logger相关联。PatternLayout可以通过c语言printf的格式来指定打印格式。
PatternLayout中,若指定conversion pattern为%-4relative [%thread] %-5level %logger{32} - %msg%n,其会按如下格式打印消息:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
参数化打印
可以通过如下形式来进行参数化打印
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有当前消息被评估应该打印时,才会通过消息模板来构造消息,将{}替换为entry的实际值。
Logback Configuration
logback配置顺序
- 如果
logback.configurationFile系统变量设置 - 如果1步骤失败,会尝试在classpath中寻找
logback-test.xml - 如果2步骤失败,会尝试在classpath中寻找
logback.xml
通过命令行System Properties设置logback配置文件位置
可以通过向命令行传递
-Dlogback.configurationFile=/path/to/config.xml来设置logback配置文件的位置
通过xml形式配置logback
<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} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
自动加载配置文件更新
logback支持对配置文件的变动进行扫描,并且对扫描到的变动进行自动配置。为了令logback支持配置自动更新,需要为configuration元素配置scan属性,示例如下所示:
<configuration scan="true">
...
</configuration>
在开启配置自动更新时,默认会每间隔1 minute就扫描配置变动。可以通过scanPeriod属性自定义扫描周期,周期单位可以是milliseconds, seconds, minutes, hours。示例如下所示:
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
如果在没有为scanPerod属性值指定单位时,默认单位为ms
xml配置文件语法
一个基础的xml配置文件可以包含如下结构:
- 一个
<configuration>元素- 0或多个
<appender>元素 - 0或多个
<logger>元素 - 最多一个
<root>元素
- 0或多个
<logger>
<logger>元素用于配置logger,其接收一个必填的name属性,一个可选的level属性,一个可选的additivity属性。
additivity属性的值可以为true或者false。level属性的值可以为TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF,是大小写不敏感的。- 如果想要从上级继承level,可以将level属性的值设置为
INHERITED或是NULL
- 如果想要从上级继承level,可以将level属性的值设置为
logger元素中可以包含0个或多个<appender-ref>元素,这样每个引用的appender都会被加入到logger中。
<root>
root元素用于配置root logger,其只支持一个属性level。由于root元素已经被命名为ROOT,故而也不允许指定name属性。
<root>元素的level属性只能被赋值为TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF,不能被赋值为INHERITED或是NULL.
和logger元素一样,root元素中也能包含0个或多个<appender-ref>元素。
<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} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO"/>
<!-- Strictly speaking, the level attribute is not necessary since -->
<!-- the level of the root level is set to DEBUG by default. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Appender
<appender>元素用于配置Appender,其接收两个必填属性:name和class。其中,name属性指定了appender的名称,而class属性则是指定了appender Class的全类名。
appender元素可以包含0或多个<layout>元素,0或多个<encoder>元素,0或多个<filter>元素。除了可以包含上述公共元素外,还appender元素还可以包含任意数量javaBean属性相关的元素,
Layout
<layout>元素接收一个必填class属性,值为Layout类的全类名。和appender元素一样,layout也可以包含javaBean属性相关的元素。如果layout class为PatternLayout,那么class属性可以省略。
encoder
<encoder>元素接收一个必填的class属性,值为Encoder的全类名。如果encoder类为PatternLayoutEncoder,那么class属性可以省略。
当想要向多个appender中输出日志时,只需要在logger中定义多个appender即可,示例如下
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] -%kvp- %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
上述配置中,定义了两个appender,FILE和STDOUT。 FILE appender将日志打印到myApp.log文件中。STDOUT appender则是将日志打印到控制台。
root logger通过<appender-ref>标签来引用appender,每个appender都有其自己的encoder,encoder通常在appender之间并不共享。同样的,layout也不会在多个appender之间共享。
由于logger不仅会将日志发送到其自身的logger,并且还会将日志发送给其先祖logger的appender,故而将相同的appender共享给拥有上下级关系的logger将会导致日志的重复打印。
可以通过设置additivity属性来设置后代logger不继承先祖logger的appender,配置示例如下所示:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>foo.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file : %line] -%kvp- %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<logger name="chapters.configuration.Foo" additivity="false">
<appender-ref ref="FILE" />
</logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
变量替换
可以以${VAR_NAME}的形式来使用变量替换,且HOSTNAME和CONTEXT_NAME变量是默认定义的。
如下示例展示了如何定义变量:
<configuration>
<variable name="USER_HOME" value="/home/sebastien" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
变量除了可以在配置文件中定义,还可以通过System Properties进行传递,
java -DUSER_HOME="/home/sebastien" MyApp2
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
当存在多个变量时,可以单独为变量定义一个文件:
<configuration>
<variable file="src/main/java/chapters/configuration/variables1.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
USER_HOME=/home/sebastien
除了通过<variable>元素的file属性指定变量文件位置外,还可以通过variable元素的resource属性指定classpath下的文件路径:
<configuration>
<variable resource="resource1.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
变量作用域
一个变量可以被定义在如下作用域:
- LOCAL SCOPE: 具有local作用域的变量,生命周期为从其文件定义该变量的位置开始,一直到该配置文件的末尾
- CONTEXT SCOPE: 具有context作用域的变量,在context存在期间一直存在,直到context执行clear操作
- SYSTEM SCOPE: 具有system作用域的变量,将会添加到jvm的system properties中,生命周期和jvm相同,直到该变量被清空
在变量替换时,变量首先从local scope中查找,其次到context scope中找,再其次到system scope中找,最后到os环境变量中找。
<variable>元素中可以含有scope属性,其值可以为local,context或system。如果没有指定scope属性,默认是local。
<configuration>
<variable scope="context" name="nodeId" value="firstNode" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/opt/${nodeId}/myApp.log</file>
<encoder>
<pattern>%kvp %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
在上述示例中,尽管nodeId变量被定义为context scope,其在每个logging event中都可以使用。
为变量赋值默认值
可以按"${aName:-golden}"的方式为指定返回的默认值,当aName变量未定义时,返回值为golden。
变量嵌套
在定义变量时,支持变量的嵌套:
USER_HOME=/home/sebastien
fileName=myApp.log
destination=${USER_HOME}/${fileName}
并且,表达式也支持嵌套变量:
"${${userid}.password}"表达式在userid变量为asahi的情况下,表示asahi.password变量的值
在为变量指定默认值时,默认值也可以通过变量来指定,例如${id:-${userid}},在id变量未定义时,会返回userid变量的值。
条件处理配置文件
logback配置文件支持通过<if>,<then>,<else>元素来条件指定配置。条件处理需要janino库。
条件处理语法如下:
<!-- if-then form -->
<if condition="some conditional expression">
<then>
...
</then>
</if>
<!-- if-then-else form -->
<if condition="some conditional expression">
<then>
...
</then>
<else>
...
</else>
</if>
条件为java表达式,且只允许访问system properties和context properties。proerpty()方法或p()方法将会返回变量的值,例如p('k')或property('k')将会返回变量k的值。如果k变量未定义,将会返回空字符串。
isDefined方法则是可以用来检查指定变量是否被定义。并且,可以通过isNull方法来检查指定变量的值是否为空。
<configuration debug="true">
<if condition='property("HOSTNAME").contains("torino")'>
<then>
<appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %-5level %logger{35} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="CON" />
</root>
</then>
</if>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${randomOutputDir}/conditional.log</file>
<encoder>
<pattern>%d %-5level %logger{35} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="FILE" />
</root>
</configuration>
Appender
logback把日志事件的写操作委托给了Appender组件。Appender必须实现ch.qos.logback.core.Appender接口,接口主要方法如下:
package ch.qos.logback.core;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.LifeCycle;
public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable {
public String getName();
public void setName(String name);
void doAppend(E event);
}
其中,doAppend方法负责将日志输出到对应的输出设备。
appender是命名实体,故而其可以通过name来进行引用。Appender实现了FilterAttachable接口,故而可以将一个或多个filter关联到appender。
appender负责将日志事件打印到输出设备,但是,appender可以将日志的格式化操作委托给Layout或Encoder对象。每个layout或encoder都只会关联一个appender,一些logger拥有内置的固定format格式,对于用于内置固定format的appender,其不需要encoder或layout。
例如,SocketAppender并没有encoder或layout,其只是将日志事件序列化,并且通过网络传输序列化后的数据
AppenderBase
ch.qos.logback.core.AppenderBase是一个实现了Appender接口的抽象类,其针对appender中的部分接口提供了实现。logback中内置的所有apender实现都实现了该抽象类。
AppenderBase类实现了doAppend方法,并留下了append方法供实现类进行实现。AppenderBase类中,doAppend方法被synchronized修饰,多线程环境下doAppend方法的调用是阻塞的。
Logback Core
logback core是其他logback模块的基础。其内置了如下开箱即用的Appender。
OutputStreamAppender
OutputStreamAppender会将日志事件追加到java.io.OutputStream中,该类为其他基于OutputSteamAppender的appender提供了基础。
ConsoleAppender和FileAppender都继承了OutputStreamAppender。
ConsoleAppender
ConsoleAppender会将日志追加到console,即System.out或System.err中,前者是默认的目标设备。ConsoleAppender通过用户指定的encoder来对日志事件进行格式化。
ConsoleAppender拥有如下property:
- encoder :
Encoder类型 - target : 字符串类型,值为
System.out或System.err - withJansi : boolean类型,默认为false、
如下为ConsoleAppender的使用示例:
<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>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
FileAppender
FileAppender是OutputStreamAppender的子类,将日志事件打印到文件中。目标文件通过File选项来指定,如果指定文件已经存在,是否清空文件内容或将日志追加到文件末尾,取决于append属性。
FileAppender拥有如下property:
- append: boolean类型,当文件已经存在时,如果设置为true,日志追加到文件末尾,如果日志设置为false,已存在文件的内容将会被清空。默认该属性被设置为true
- encoder:Encoder类型
- file:字符串类型,为写入文件的文件名,如果文件不存在,那么文件会被创建。如果该路径值中存在不存在的目录,FileAppender会自动创建目录。
- bufferSize:FileSize类型,当immediateFlush属性被设置为false时,可以通过bufferSize选项将会设置output buffer的大小。bufferSize的默认值为8192。在定义FileSize类型时,可以按
KB, MB, GB来指定,只需要以5MB形式指定即可。在没有指定后缀单位时,默认为字节 - prudent: 当prudent属性设置为true时,会将
append属性设置为true。prudent依赖与排他的file lock,在开启prudent后,写日志开销通常是prudent关闭开销的3倍。
Immediate flush
默认情况下,每个log事件都会立即刷新到outputstream中,这样可以避免日志的丢失(如果日志刷新存在延迟,那么应用在未刷新的情况下当即可能会使缓存中的日志丢失)。
当时,如果要提高吞吐量,可以将
immediateFlushproperty设置为false。
配置示例如下所示:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>testFile.log</file>
<append>true</append>
<!-- set immediateFlush to false for much higher logging throughput -->
<immediateFlush>true</immediateFlush>
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
<timestamp>
对于短期应用,可能会期望在每次程序运行时,都创建唯一的日志文件。可以通过<timestamp>元素来实现该需求,示例如下:
<configuration>
<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
the key "bySecond" into the logger context. This value will be
available to all subsequent configuration elements. -->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- use the previously created timestamp to create a uniquely
named log file -->
<file>log-${bySecond}.txt</file>
<encoder>
<pattern>%logger{35} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
timestamp元素接收两个必填属性,key和datePattern,并接收一个非必填的属性timeReference。
- datePattern : 格式和
SimpleDateFormat相同 - timeReference : 默认情况下,timestamp元素的值为当前配置文件被解析的事件,也可以将其设为
contextBirth,即context创建时间