在logback学习笔记(上)中我们介绍了logback中的一些核心概念,在这篇文章中我们共同来学习一下如何利用配置文件来在项目中集成logback。通过本篇博客的学习相信大家能够对logback有一个更加清晰的认识,不仅能够读懂一般项目中的日志配置并且能够自己完成一个简单的logback日志配置。这篇博客接logback学习笔记(上),章节就续上篇了。
三. 通过配置文件集成logback
1. 从一个简单logback配置文件说起
上面是一个logback配置文件简单demo。配置文件以xml文件进行组织,最外层用一个<configuration></configuration>包裹;然后定义了两个appender,一个appender用于将日志输出到控制台,一个appender用于将日志输出到文件;通过一个<include/>引入了一个名为include.xml文件,include.xml文件中也定义了一个appender,appender名为FILE;<logger></logger>定义了名称为“com.logback.demo.ConfigureLogbackDemo”的logger,日志级别为info, appender-ref引用的是名为FILE的appender,FILE在include.xml文件中定义;最后是root logger,root looger比较特殊,直接使用<root></root>进行定义。
logback配置文件在java工程的类路径resources目录下,在logback日志框架启动时。首先尝试加载logback-test.xml,如果找到就用该文件初始化logback;否则尝试加载logback.groovy配置文件,logback支持groovy格式的配置,如果找到就用logback.groovy初始化;否则尝试加载logback.xml,如果有该文件则用logback.xml初始化;否则使用logback提供的默认初始化方式。我们统一使用logback.xml进行配置。
2. <configuration/>的配置内容
<configuration/>是整个xml配置文件的最外层。configuration可以配置两个属性,debug和scan,都是bool值。debug用于控制logback启动时在控制台打印上下文信息,默认为false。或者在configuration下加入<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>也能实现同样的效果。
设置debug="true"能够帮我们进行配置的调试,我们来看一下将debug设置为true后执行一次日志打印logback控制台打印了什么。可以看到输出了很多内容,截取部分进行说明。看日志内容我们基本能看出其含义,第一行说明无法找到logback-test.xml,第二行说明无法找到logback.groovy,第三行说找到了logback.xml等等。这正是logback日志初始化的过程,这说明通过这个debug不仅能够看出来logback在启动阶段做了什么,还能在我们进行日志配置调试时提供帮助。
configration的scan和scanPeriod配置多久重新加载一次配置文件,在日志服务启动后可能修改配置文件。通过配置scan="true"可以周期性的重新加载logback配置文件,重新初始化logback上下文。scanPeriod是刷新频率。
3. 在配置文件中定义appender
appender用于定义日志的输出位置,如输出到控制台可以使用ConsoleAppender,输出到文件可以使用FileAppender或者RollingFileAppender,输出到SocketAppender等等。我们先以FileAppender为例说明Appender中的一些常用配置项。
如上是logback.xml中所引入的include.xml文件的定义,在这个xml文件中只定义了一个<appender/>,其类型是FileAppender。可以看到appender会有一个name属性为其赋名称,calss属性指定使用哪个appender,这里使用的是FileAppender。也可以自己定义一个Appender类,感兴趣的同学可以试一试。在FileAppender中通过<file>demo.log</file>指定日志文件名,本例中是以相对路径输出到项目根目录下,也可以用绝对路径。<append/>说明是以累加还是以覆盖形式将日志内容输出到日志文件中。<immediateFlush/>用于控制是否将日志事件的输出内容立即刷新到输出流,立即刷新可以确保在应用程序退出而没有正确关闭Appender的情况下日志输出内容不会丢失。也可以将其设置为false提升日志吞吐率。
接下来是<encoder/>,encoder用于定义日志内容格式化的pattern。在logback学习笔记(上)中我们曾提到layout和encoder都可以对日志内容进行格式化,logback早期版本是通过layout对日志内容进行格式化,从logback0.9.19版本后新增了encoder的实现方式。看文档中说是encoder比layout更有优势,不过所说的具体优势我还是不太理解。不过既然是新增的一种实现方式,我们就认为encoder比layout更好用一些吧。在上面所展示的例子中<encoder></encoder>所指定的类是ch.qos.logback.classic.encoder.PatternLayoutEncoder,PatternLayoutEncoder可以兼容Layout中的pattern模式,一般来说使用encoder时只用PatternLayoutEncoder这一个类就足够了。pattern定义了日志输出格式,在例子中也做了注释,这里就不再赘述。
最后一个是<filter></filter>。顾名思义filter应该是对日志进行过滤的,我们看到filter的class属性设置的是一个LevelFilter,对日志打印级别进行过滤。对于每种类型的filter都会有一个onMatch方法和onMismatch方法,对匹配和不匹配过滤条件时执行过滤操作。对于这里的LevelFilter来说,定义了一个只过滤ERROR级别日志的过滤器。当输出日志是ERROR级别是就ACCEPT,否则就DENY,即只输出ERROR日志。
include.xml文件中用FileAppender定义的appender介绍完了。鉴于我们在项目中会经常用到根据不同的条件对日志文件进行归档的RollingFileAppender。这里再通过一个appender定义说明一下RollingFileAppender中的一些常见的配置属性。
RollingFileAppender根据rollingPolicy对日志文件进行归档,这里使用的归档策略是基于日志的生成时间进行归档。通过TimeBaseRollingPolicy的<fileNamePattern/>对日志文件进行命名,以时间戳%d{yyyyMMddHHmm}作为日志文件的后缀,就可以在日志打印时根据时间生成相应的日志文件名。如下是通过这个ROLL_FILE这个appender在不同分钟执行日志打印时产生的日志文件。我这里设置为按分钟生成是为了测试方便,在生产环境中配置时我们可以根据需要按天生成%d{yyyyMMdd}或者按小时生成%{yyyyMMddHH}都可以。其他几个属性都做了注释,不再赘述。
4. 在配置文件中定义loggger
logback配置文件中可以配置任意个<logger/>,除了root logger之外,其他logger都必须要配置name属性来指定logger的名称。在logback学习笔记(上)中曾介绍过root logger有一个默认且不可变的name="root",因此root logger无需指定name属性。对于每个定义的logger可以根据需要设置logger的日志打印级别(trace/debug/info/warn/error)和appender,一个logger可以配置多个appender,表示同时将日志输出到多个目的终端。<appender-ref />引用的是在我们在配置文件中定义的appender,通过名称引用。
一般情况下我们为logger指定的name属性都是其包路径或者全路径类名,这样就可以将我们定义的logger应用到包路径或者类路径下的类中。我们知道logback的logger属性(主要指logger的appender和日志打印级别属性)具有继承性,即父层级定义的日志打印级别和appender可以被子层级继承使用,因此只要我们将配置文件中定义的logger的name属性设置为某个包的包路径,就可以将该logger应用到指定包下所有类的日志打印中。
如果我们在父层级和子层级的logger中都指定了同一个appender,那么在进行日志打印时我们将会看到一个日志打印在同一个appender中输出了两次,如下例子我们分别在root logger和名为com.logback.demo.ConfigureLogbackDemo的logger中同时指定了STDOUT这个appender,STDOUT是一个输出到控制台的appender定义。我们在ConfigureLogBackDemo中打印日志可以看到同一个日志打印在控制台被输出了两次,这就是层级间appender继承导致的。名为com.logback.demo.ConfigureLobackDemo的logger是root logger的子logger,所以它继承了root logger的appender,同时自己也引用了一次名为STDOUT的appender,所以我们看到了两次控制台打印。
如果想要阻断层级间的继承可以为logger设置additivity="false"属性,如下所示。我们又在配置文件中添加了两个logger定义,分别是name="com.logback"和name="com.logback.demo"。根据logger的继承规则:com.logback.demo是com.logback.demo.ConfigureLogbackDemo的父层级;com.logback又是com.logback.demo的父层级。在不加additivity="false"时,再次执行日志打印时将在控制台输出四条打印,而在com.logback.demo加上additivity="false"后会输出两条打印,在com.logback上加上additivity="false"时输出三条打印,在root上添加additivity="false"仍会输出四条打印。可以看到additivity="false"对root并不起作用,而对于普通logger定义则可以阻断其appender向下层的继承。
logger定义中除了appender的继承外还包括日志打印级别level的继承,在上篇中有讲到作用规则,大家可以自己试试。
5. 用<property/>定义属性值
利用<property/> 标签可以在 配置文件中定义属性值,然后根据属性的scope可以在相应的范围内访问所定义的属性值。如下图所示:我们用<property/> 定义了一个name="date_pattern",value="%{yyyyMMdd}"的属性。然后在appender的<fileNamePattern/>中配置日志文件的名字时通过${date_pattern}引用了属性值。这里我们重新定义了一个名为logback-test.xml的配置文件,根据前面介绍的内容logback会首先尝试加载名为logback-test.xml的配置文件。我们再次执行日志打印后可以看到在项目的相对目录下生成了一个名为rolling.log.20180806的日志文件,并输出了日志内容。${date_pattern}被替换为%{yyyyMMdd},%{yyyyMMdd}表示执行的日期。
除了name和value之外,<property/>还有一个scope属性。scope用于定义property的作用域,有三种作用域可供选择:local、context、system,默认作用域是local,local表示该变量定义只在本配置文件中有效,context表示变量在整个logback上下文中都有效,system则表示将变量加入到System中。三种作用域的范围是local < context < system。在下面代码中展示了获取context作用域和system作用域中的property值。
6. 在配置文件中使用condition条件
有时候我们可能会根据条件的不同有不同的logback配置,就像是我们在代码中的if else那样,不同的条件执行不同的逻辑,logback为我们提供了这样的支持。logback condition的基本结构如下:
condition是一个boolean判断,如果返回true则执行then的配置,否则执行else中的配置。举个栗子:我们需要根据是开发环境还是生产环境将日志输出到不同的appender,那我们可以采用类似如下的配置进行实现。
condition有几个常用的表达式,isDefine("key")用于判断key是否被定义、isNull("key")判断是否为null、p("key").contains('value')判断参数中是否包含value字符串。判断的值存在于context和JVM System属性中,如我们可以在服务启动时将参数初始化到System中(通过System.setProperty),然后通过logback的condition子句针对不同的条件进行配置。
写在最后:到这里logback学习笔记所要介绍的内容就完结了,在这两篇笔记中,上篇主要介绍一些核心概念和简单的API,下篇介绍了通过配置文件集成logback的一些比较重要的配置。仅仅通过这两篇文章是不可能把logback的全部内容介绍详尽,但我还是希望看到的同学能够有所收获。(这两篇博客用的是富文本编辑,不能贴代码,只能通过图片的方式展示,体验上不是太好。今天发现简书可以通过Markdown编辑,支持代码,以后的文章会用Markdown模式编辑,敬请期待。)
你的关注是我持续更新的动力。