(二)logback详解之配置
Logback里的配置
把记录请求插入程序代码需要相当多的计划和努力。有观察显示大约4%的代码是记录。所以即使是一个中等规模的应用程序也会包含数以千计的记录语句。考虑到数量庞大,我们需要使用工具来管理记录语句。
Logback可以通过编程式配置,或用XML格式的配置文件进行配置。 Logback采取下面的步骤进行自我配置:
1. 尝试在classpath下查找文件logback-test.xml;
2. 如果文件不存在,则查找文件logback.xml;
3. 如果两个文件都不存在,logback用BasicConfigurator自动对自己进行配置,这会
导致记录输出到控制台。
第三步也是最后一步是为了在缺少配置文件时提供默认(但基本的)记录功能。
自动配置
最简单的配置方法就是使用默认配置。 BasicConfigurator用法的简单例子:
((logback-examples/src/main/java/chapters/configuration/MyApp1.java))
package manual.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApp1 {
final static Logger logger = LoggerFactory.getLogger(MyApp1.class); public static void main(String[] args) {
logger.info("Entering application.");
package manual.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApp1 {
final static 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.");
}
}
假设配置文件logback-test.xml和logback.xml都不存在,那么logback默认地会调用BasicConfigurator,创建一个最小化配置。最小化配置由一个关联到根logger的ConsoleAppender组成。输出用模式为%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n的PatternLayoutEncoder进行格式化。还有,根logger默认级别是DEBUG。
用logback-test.xml或logback.xml自动配置
前面提到过,如果classpath里有logback-test.xml或logback.xml,logback会试图用它进行自我配置。下面的配置文件与刚才的BasicConfigurator等效。
示例:基本配置文件(logback-examples/src/main/java/chapters/configuration/sample0.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>
自动打印警告和错误消息
当解析配置文件有警告或出错时,logback会在控制台上自动打印状态数据。如果没有警告或错误,你还是想检查logback的内部状态的话,可以调用StatusPrinter的print()方法。MyApp2程序等价于MyApp1,只是多了两行打印内部状态数据的代码。
示例:打印logback的内部状态信息
(logback-examples/src/main/java/chapters/configuration/MyApp2.java)
public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory(); // print logback's internal status
StatusPrinter.print(lc);
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
在输出的最后面,你可以看到上例输出的内容。你也应当注意到logback的内部消息,也就是Status对象,它可以方便地访问logback的内部状态。
可以不用从代码里调用StatusPrinter,而是在配置文件里进行相关配置,即使没有出现错误。方法是,设置configuration元素的debug属性为true。请注意debug属性只与状态数据有关,它不影响logback的配置,更不会影响记录级别。
示例:debug模式的基本配置
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned by default the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder
-->
<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>
把configuration元素的debug属性设为true后,会输出状态信息,但是前提是: 1. 找到了配置文件;
2. 配置文件是格式化良好的XML。
如果其中任一条件未满足,Joran就会因为配置文件不可读而无法读取debug属性。如果找到了配置文件,但却不是格式化良好的,那么logback会检测出错误并把内部状态打印到控制台。然而,如果找不到配置文件,由于这不是个严重的错误,logback不会自动打印状态数据。使用编程式的主动调用StatusPrinter.print()可以确保始终打印状态信息,如MyApp2。
把默认配置文件的位置作为系统属性进行指定
设置名为logback.configurationFile的系统属性,把默认配置文件的位置作为属性值,这种方法也可以。属性值即配置文件位置可以是个URL、classpath里的一个资源,或者是程序外部的文件路径。
java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1
配置文件修改后自动重新加载
如果设置成自动重新加载,logback-classic会扫描配置文件里的变化,并且当发生变化后进行重新配置。设置访方法是设configuration元素的scan属性为true。
示例:扫描配置文件的变化并自动重新配置
(logback-examples/src/main/java/chapters/configuration/scan1.xml)
<configuration scan="true"> ...
</configuration>
默认情况下,每隔一分钟扫描一次。configuration元素的scanPeriod属性控制扫描周期,其值可以带时间单位,包括:milliseconds、seconds、minutes和hours。
示例:指定不同的扫描周期
(logback-examples/src/main/java/chapters/configuration/scan2.xml)
<configuration scan="true" scanPeriod="30 seconds"> ...
</configuration>
如果没写明时间单位,则默认为毫秒。
考虑到在任何logger在每次被调用时都要调用ReconfigureOnChangeFilter,这个过滤器的性能就变得十分关键了。为提高性能,不会在每个logger被调用时去检查是否需要扫描,而是每隔16次记录操作进行一次检查。简言之,当配置文件改变后,它会被延时重新加载,延时时间由扫描间隔时间和一些logger调用所决定。
直接调用JoranConfigurator
Logback依赖Joran,Joran是logback-core的一部分,是个配置类库。Logback的默认配
置机制是调用JoranConfigurator对classpath上的默认配置文件进行处理。不管出于什么理由,如果你想重新实现logback的默认配置机制的话,你可以直接调用JoranConfigurator。下面没的程序MyApp3就调用了JoranConfigurator对作为参数传入的配置文件进行处理。
示例:直接调用JoranConfigurator
(logback-examples/src/main/java/chapters/configuration/MyApp3.java)
package chapters.configuration;
/**
* Demonstrates programmatic invocation of Joran. * */
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter;
public class MyApp3 {
final static Logger logger = LoggerFactory.getLogger(MyApp3.class);
public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment
LoggerContext lc = (LoggerContext)
LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
// the context was probably already configured by default
// configuration rules
lc.reset();
configurator.doConfigure(args[0]); } catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
}
本程序直接取得LoggerContext,创建新JoranConfigurator并设置它要操作的上下文,重置logger上下文,最后要求配置器用参数中的配置文件对上下文进行配置。同时打印了内部状态数据。
查看状态消息
Logback把内部数据放在一个StatusManager对象里,并通过LoggerContext访问。
StatusManager通过logback上下文来访问所有数据对象。为把内存占用保持在合理的范围内,默认的StatusManager实现将状态消息按头和尾两部分存储。头部存储开始的H条状态消息,尾部存储后面的T条消息。现在的H=T=150,将来或许会改变。
Logback-classic带了一个叫ViewStatusMessagesServlet的Servlet,它以HTML表格的格式打印与当前LoggerContext关联的StatusManager的内容。示例如下。
要加到自己的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>
访问地址是
http://host/yourWebapp/lbClassicStatus。
监听状态消息
你也可以为StatusManager附加一个StatusListener,这样就能立即对状态消息作出响应,尤其对那些logback配置完成之后的消息。注册一个状态监听器可以方便地实现对logback内部状态的无人监管。
Logback带了一个叫OnConsoleStatusListener的StatusListener实现,可以把状态消息打印到控制台。
下例演示了如何为StautsManager注册一个OnConsoleStatusListener实例。
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusManager statusManager = lc.getStatusManager();
OnConsoleStatusListener onConsoleListener = new OnConsoleStatusListener();
statusManager.add(onConsoleListener);
注意注册了的状态监听器只会接收被注册之后的状态消息,不会注册之前的消息。
也可以在配置文件里注册一个或多个状态监听器。如下面的例子。 示例:注册状态监听器
(logback-examples/src/main/java/chapters/configuration/onConsoleStatusListener.xml)
<configuration>
<statusListener
class="ch.qos.logback.core.status.OnConsoleStatusListener" /> ... the rest of the configuration file </configuration>
还可以通过设置Java系统属性“logback.statusListenerClass”注册状态监听器,例如,
java -Dlogback.statusListenerClass=ch.qos.logback.core.status.OnConsoleStatusListener ...
配置文件语法
到目前为止,正如你已经在这份手册里看到的不少例子,logback允许你重新定义记录行为而不必重新编译你的代码。实际上,你可以轻易地配置logback,比如禁用程序里某些地方的记录功能,或者直接输出到一个UNIX系统守护进程、数据库、日志查看器,或把记录事件发送到远程logback服务器,远程logback服务器按照其本地策略进行记录,比如把记录时间发送到第二个logback服务器。
Logback配置文件的语法非常灵活。正因为灵活,所以无法用DTD或XML schema进行定义。尽管如此,可以这样描述配置文件的基本结构:以<configuration>开头,后面有零个或多个<appender>元素,有零个或多个<logger>元素,有最多一个<root>元素。如下图所示:
标记名大小写敏感性
从logback 0.9.17版起,标记名不区分大小些。比如,<logger>、<Logger>和<LOGGER>都是合法元素且表示同一个意思。按照隐式规则,标记名除了首字母外要区分大小写。因此,<xyz>与<Xyz>等价,但不等价于<xYz>。隐式规则一般遵循Java世界里常用的驼峰命名规则。因为很难确定一个标记什么时候与显式动作相关,什么时候又与隐式动作相关,所以很难说XML标记是否是大小写敏感。如果你不确定标记名的大小写,就用驼峰命名法,基本不会错。
配置logger,或<logger>元素
Logger是用<logger>元素配置的。<logger>元素有且仅有一个name属性、一个可选的level属性和一个可选的additivity属性。
Level属性的值大小写无关,其值为下面其中一个字符串:TRACE、DEBUG、INFO、WARN、ERROR、ALL和OFF。还可以是一个特殊的字符串“INHERITED”或其同义词
“NULL”,表示强制继承上级的级别。
<logger>元素可以包含零个或多个<appender-ref>元素,表示这个appender会被添加到该logger。强调一下,每个用<logger>元素声明的logger,首先会移除所有appender,然后才添加引用了的appender,所以如果logger没有引用任何appender,就会失去所有appender。
配置根logger,或<root>元素
<root>元素配置根logger。该元素有一个level属性。没有name属性,因为已经被命名为“ROOT”。
Level属性的值大小写无关,其值为下面其中一个字符串:TRACE、DEBUG、INFO、WARN、ERROR、ALL和OFF。注意不能设置为“INHERITED” 或“NULL”。
<logger>元素可以包含零个或多个<appender-ref>元素。与<logger>元素类似,声明<root>元素后,会先关闭然后移除全部当前appender,只引用声明了的appender。如果root元素没有引用任何appender,就会失去所有appender。
配置Appender
Appender用<appender>元素配置,该元素必要属性name和class。 name属性指定appender的名称,class属性指定appender类的全限定名。
<appender>元素可以包含零个或多个<layout>元素、零个或多个<encoder>元素和零个或多个<filter>元素。除了这三个常用元素之外,还可以包含appender类的任意数量的javabean属性。下图演示了常用结构,注意对javabean属性的支持在图中不可见。
<layout>元素的class属性是必要的,表示将被实例化的layout类的全限定名。和<appender>元素一样,当layout是PatternLayout时,可以省略class属性。
<encoder>元素class属性是必要的,表示将被示例化的encoder类的全限定名。因为太常用了,所以当encoder是PatternLayoutEncoder时,也可以省略class.
记录输出到多个appender很简单,先定义各种appender,然后在logger里进行引用就行了。
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<!-- encoders are assigned by default the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder
-->
<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>
该配置文件定义了两个appender,分别是“FILE”和“STDOUT”。
“FILE”这个appender把记录输出到文件“myapp.log”,它的encoder是PatternLayoutEncoder,输出了日期、级别、线程名、logger名、文件名及记录请求的行号、消息和行分隔符。
“STDOUT”这个appender把记录输出到控制台,它的encoder只是输出消息和行分隔符。
注意每个appender都有自己的encoder。Encoder通常不能被多个appender共享,layout也是。所以,logback的配置文件里没有共享encoder或layout的语法。
Appender累积
默认情况下,appender是可累积的:logger会把记录输出到它自身的appender和它所有祖先的appender。因此,把同一appender关联到多个logger会导致重复输出。Appender的叠加性对新手来说并不是陷阱,反而是非常方便的。举例来说,你可以让某些系统里所有logger的记录信息出现在控制台,却让某些特定logger的记录信息发到一个特定的appender。
覆盖默认的累积行为
如果你觉得默认的累积行为不合适,可以设置叠加性标识为false以关闭它。这样的话,logger树里的某个分支可以输出到与其他logger不同的appender。
示例:叠加性标识
(logback-examples/src/main/java/chapters/configuration/additivityFlag.xml)
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>foo.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>
<logger name="chapters.configuration.Foo" additivity="false">
<appender-ref ref="FILE" /> </logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
本例中,logger“chapters.configuration.Foo”关联appender“FILE”,它的叠加性标记为false,这样它的记录输出仅会被发送到appender“FILE”,不会被发送到更高logger等级关联的appender。其他logger不受此影响。
设置上下文名称
<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>
变量替换
原则上,指定变量的地方就能够发生变量替换。变量替换的语法与Unix shell中的变量替换相似。位于“${”与“}”之间的字符串是键(key),取代键的值可以在同一配置文件里指定,也可以在外部文件或通过系统属性进行指定。例如,如果设系统属性“java.home.dir”为“/home/xyz”,那么每次当${java.home.dir}出现时都会被解释为“/home/xyz”。Logback自动定义了一个常用变量“${HOSTNAME}”。
属性被插入logger上下文
注意通过<property>元素定义的值实际上会被插入logger上下文。换句话说,这些值变成了logger上下文的属性。所以,它们对所有记录事件都可用,包括通过序列化方式被发送到远程主机的记录事件。
下面的例子在配置文件的开头声明了一个变量又名替换属性,它代表输出文件的位置,然后在后面的配置文件里使用它。
示例:简单变量替换
<configuration>
<property name="USER_HOME" value="/home/sebastien" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
下一个例子用系统属性实现了同样的功能。属性没有在配置文件里声明,因此logback会从系统属性里找。Java系统属性用下面的命令行进行设置:
<pattern>%msg%n</pattern>
</encoder>
</appender>
java -DUSER_HOME="/home/sebastien" MyApp2
示例:系统变量替换
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
当需要很多变量时,更方便的做法是在一个单独的文件里声明所有变量,如下例所示。 示例:文件变量替换
<configuration>
<property
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>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
这个配置文件包含对文件“variables1.properties”的引用,该文件里的变量会被读入logback配置文件的上下文里。
文件“variables1.properties”内容类似于:USER_HOME=/home/sebastien
还可以不引用文件,而是引用class path上的资源。
<configuration>
<property resource="resource1.properties" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
嵌套变量替换
Logback支持嵌套变量替换。这里的嵌套是指变量的值里包含对其他变量的引用。假设你希望用变量指定目的地目录和文件名,然后用一个变量“destination”组合这两个变量,如下面所示。
示例:嵌套变量替换
USER_HOME=/home/sebastien fileName=myApp.log
destination=${USER_HOME}/${fileName}
变量的默认替换值
在某些特定情况下,最好给变量一个默认值,以免变量未被声明或值为null。Bash shell用“:-”指定默认值。例如,假设“aKey”未被声明,那么“${aKey:-golden}”将被解释为“golden”。
配置文件里的条件化处理
开发者经常需要针对不同的环境在不同的配置文件里换来换去,比如开发、测试和生产环境。这些配置文件大同小异。为避免重复劳动,logback支持在配置文件里进行条件化处理,用<if>、<then>和<else>这些元素可以让一个配置文件适用于多个环境。
条件语句一般格式如下。
<configuration> <!-- 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>
</configuration>
其中“condition”是java表达式,只允许访问上下文属性和系统属性。对于作为参数传入的键,property()方法或其等价的p()方法将返回属性的字符串值。例如,想访问属性键为“k”的值,你可以用property("k")或等价的p("k")。如果键为“k”的属性未被定义,property方法将返回空字符串而不是null,这样避免了检查null值。
下一个例子里,ConsoleAppender被关联到根logger,但是前提条件是HOSTNAME属性的值是“torino”。注意名为“FILE”的FileAppender在任何情况下都被关联到根logger。
<configuration>
<if condition='property("HOSTNAME").contains("torino")'>
<then>
<appender name="CON"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %-5level %logger{35} - %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} - %msg %n</pattern>
</encoder> </appender>
<root level="ERROR">
<appender-ref ref="FILE" />
</root>
</configuration>
<configuration>元素之内的任何地方都支持条件化处理。也支持嵌套的if-then-else语句。然而,加入过多的条件语句会导致XML文件非常难读。
文件包含
Joran支持在配置文件里包含其他文件。方法是声明<include>元素,如下所示: 示例:文件包含
<configuration>
<include
file="src/main/java/chapters/configuration/includedConfig.xml" />
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
被包含的文件必须把它的元素嵌套在<included>元素里。例如,可以这样声明ConsoleAppender:
<included> <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
请再次注意<included>元素是必需的。
被包含的内容可以是文件、资源或URL。
作为文件
用“file”属性包含一个文件。可以用相对路径,但是需要注意,当前目录是由应用程序决定的,与配置文件的路径必要的联系。
作为资源
用“resource”属性包含一个资源,也就是在class path上的文件。
<include resource="includedConfig.xml" />
作为URL
用“url”属性包括一个URL。
<include url="http://some.host.com/includedConfig.xml" />