(二)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" />

posted @ 2017-01-16 19:25  青青子衿J  阅读(774)  评论(0)    收藏  举报