logback–进阶–04–配置
代码位置
https://gitee.com/DanShenGuiZu/learnDemo/tree/master/logback-learn
1、加载配置的步骤
1.1、步骤
步骤1
Logback 尝试在 classpath 中找一个名为 logback-test.xml 的文件 。
步骤2
如果找不到此类文件,则 logback 尝试在 classpath 中找一个名为 logback.groovy 的文件 。
步骤3
如果找不到这样的文件,它将在 classpath 中找一个名为 logback.xml 的文件。
步骤4
如果还没有找到这样的文件, ServiceLoader会通过 META-INF\services\ch.qos.logback.classic.spi.Configurator 加载 com.qos.logback.classic.spi.Configurator 接口的实现类。
步骤5
- 如果以上方法均未成功,则 logback 将使用 BasicConfigurator 进行自动配置,这会将日志输出定向到控制台。
- 调用 BasicConfigurator ,创建一个最小化配置。
- 最小化配置由一个关联到根 logger 的 ConsoleAppender 组成。输出用模式为 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 进行格式化。
- 默认情况下,为 root logger 分配了 DEBUG 级别。
1.2、案例1
演示没有配置文件,logback 使用默认配置的效果
1.2.1、代码
public class MyApp1 {
private static final Logger LOGGER = LoggerFactory.getLogger(MyApp1.class);
public static void main(String[] args) {
LOGGER.info("我是在 MyApp1 类中,使用info级别打印日志");
User user = new User();
user.say();
}
}
public class User {
private static final Logger LOGGER = LoggerFactory.getLogger(User.class);
public void say() {
LOGGER.debug("我是在 User 类中,使用debug级别打印日志");
}
}
1.2.2、运行程序,控制台输出日志如下
10:30:25.543 [main] INFO fei.zhou.logbacklearn.business.test.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志
10:30:25.563 [main] DEBUG fei.zhou.logbacklearn.business.test.User - 我是在 User 类中,使用debug级别打印日志
1.3、案例2
演示使用 logback.xml 进行配置
1.3.1、 logback.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>
1.3.2、 再运行上面的程序,控制台会输出如下信息
10:36:33.698 [main] INFO f.z.l.business.test.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志
10:36:33.705 [main] DEBUG f.z.logbacklearn.business.test.User - 我是在 User 类中,使用debug级别打印日志
2、打印内部状态
2.1、介绍
如果程序在解析配置文件期间发生警告或错误,则 logback 会自动在控制台上打印其内部状态信息。如果在没有警告或错误时,你也希望检查 logback 的内部状态,你可以指示通过调用 StatusPrinter 类的 print() 方法。如下所示:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
如果你不想在程序中编写打印 logback 的内部状态,那可以在配置文件 configuration 元素的 debug 属性设置为 true,同样也可以在程序启动时打印 logback 内部状态。
<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>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
2.2、案例
3、直接指定配置文件的位置
- 通过配置系统属性(logback.configurationFile)的方式,指定 logback配置文件的位置。
- 系统属性的值
- 可以是 URL
- 可以是 类路径上的资源
- 可以是 应用程序外部文件的路径。
3.1、案例
java -Dlogback.configurationFile=/path/to/logback.xml fei.zhou.logbacklearn.business.test.MyApp1
4、自动重新加载配置文件
如果开启了自动重新加载配置文件,logback-classic 会扫描配置文件中的更改,并在配置文件更改时自动重新配置自身。在 < configuration> 标签中将 scan 属性设置为 true 即可开启。
当将 scan 属性设置为 true 时,在后台 ReconfigureOnChangeTask 会在单独的线程中运行,它会检查配置文件是否已更改。
由于在编辑配置文件时很容易出错,因此如果最新版本的配置文件具有 XML 语法错误,则它将回退到先前没有 XML 语法错误的配置文件。
<configuration scan="true">
...
</configuration>
默认情况下,每1分钟扫描一次配置文件是否有更改。我们可以设置 < configuration> 标签中的 scanPeriod 属性来指定扫描周期。单位可以为毫秒,秒,分钟或小时。如果未指定时间单位,则时间单位默认为毫秒
<configuration debug = "true" scan="true" scanPeriod="30 seconds">
<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>
5、在堆栈跟踪中启用包数据
注意从版本1.1.4开始,包装数据默认为禁用。可按如下配置开启包数据:
<configuration packagingData="true">
...
</configuration>
当然,也可以在程序中进行配置,如下:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);
如果开启了,logback 会在输出的堆栈行中显示它是属于哪个 jar 或者哪个类的。
此信息由 jar 文件的名称和版本组成,表明堆栈信息来源于此。
此机制对于识别软件版本问题非常有用。但是,计算成本相当昂贵,尤其是在经常引发异常的应用程序中。以下演示开启的结果,即多了 [] 括号内的信息。
14:28:48.835 [btpool0-7] INFO c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
6、停止 logback-classic
为了释放 logback-classic 资源,停止 logback context 是一个好主意。如果停止,会关闭所有 loggers 关联的 appenders,并有序的停止所有活动线程。
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
7、配置文件语法
7.1、 元素
一个 logger 记录器可以使用 元素配置。
< logger> 元素中,name 属性是必须的,level 级别属性是可选的,additivity 可叠加性属性也是可以选的(它的值是 true 或 false)。级别 level 属性的值是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF。还有不区分大小写的值 INHERITED 或其同义词 NULL,代表将强制从层次结构中较高的层次继承记录器继承级别。
< logger> 元素里面可包含0或多个 < appender-ref> 元素,这样引用的 appender 会关联到此 logger。不同 log4j,即便你在配置文件配置了 logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。
7.2、配置 root logger,< root> 元素
< root>元素用来配置 root logger。
它支持单个属性,即 level 级别属性。
它没有其他属性,因为可叠加性标志不适用于根记录器。
由于根记录器已被命名为 " ROOT" ,因此它也不允许使用 name 属性。
level 属性的值可以是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。但是,根记录器的级别不能设置为 INHERITED 或 NULL。
与 < logger> 元素类似,< root> 元素也可以包含零个或多个 < appender-ref>元素。如此引用的每个附加程序都会添加到根记录器中。不同 log4j,即便你在配置文件配置了 root logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。
7.2.1、案例1
下面演示个 demo,假设我们不想打印 “com.nobody.entity” 包下任何组件的任何 DEBUG 消息。可以按如下配置:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder 默认被分配 ch.qos.logback.classic.encoder.PatternLayoutEncoder 类 -->
<!-- 当然你也可以通过 class 属性 显示指定,即 <encoder class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.nobody.entity" level="INFO"/>
<!-- 其实此level属性设置也可以去除,因为默认就是 DEBUG 级别 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
7.2.2、案例2
我们可以根据需要配置任意数量的记录器。如下,我们将 com.nobody.A 记录器的级别设置 为 INFO,但同时将 com.nobody.B 记录器的级别设置为DEBUG。
<configuration>
...
<logger name="com.nobody.A" level="INFO"/>
<logger name="com.nobody.B" level="DEBUG"/>
...
</configuration>
7.3、配置 Appenders
一个 appender 使用 < appender> 元素配置,该元素具有两个必填属性 name 和 class。
1. name 属性指定 appender 的名称
2. class 属性指定实例化此 appender 的类。
< appender> 元素可包含零个或一个 < layout> 元素,零个或多个 < encoder> 元素,零个或多个层 < filter> 元素。
除了这三个公共元素之外,< appender> 可以包含任意数量的与 appender 类的JavaBean属性相对应的元素。
< layout> 有个必填的属性指定实例化此对象的全限定类名。和 < appender>一样,它也有自己的相关属性。PatternLayout 有默认的属性值,所以可以不指定属性值。
< encoder>有个必填的属性指定实例化此对象的全限定类名。PatternLayoutEncoder 有默认的属性值,所以可以不指定属性值。
<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
上述配置文件定义了两个命名为 FILE 和STDOUT 的 appender 。
1. FILE appender 将日志输出到 myApp.log 文件。
2. STDOUT appender 将日志输出到控制台。
默认情况下,附加程序是累积式的:记录器将记录到附加到其自身的附加程序(如果有)以及附加到其祖先的所有附加程序。因此,将同一附加程序附加到多个记录器将导致记录输出重复。
默认情况下,appender 是累积式的:一个 logger 会将日志输出到它自己关联的所有 appender 和 它上层级(祖先)所关联的所有 appender。所以,如果将同一个 appender 关联到不同的 logger,有可能会导致输出的日志会重复。例如下面这个例子:
<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="fei.zhou.logbacklearn.business.test.Main">
<appender-ref ref="STDOUT"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
logger.info("Hello Logback!");
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
rootLogger.info("rootLogger:{}", rootLogger.getName());
}
}
会在控制台输出如下结果,因为名为 fei.zhou.logbacklearn.business.test.Main 的 logger 的可叠加性标识默认为 true,所以会将日志输出到它上级的 logger 关联的 appender 中,所以输出2遍。而 root logger 没有上级,输出1遍。
790 [main] INFO f.z.l.business.test.Main - Hello Logback!
790 [main] INFO f.z.l.business.test.Main - Hello Logback!
803 [main] INFO ROOT - rootLogger:ROOT
当然,你可以将 com.nobody.Main 的 logger 的可叠加性标识默认为 false,那它的日志就不会输送到上层级中。
<logger name="fei.zhou.logbacklearn.business.test.Main" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
8、设置上下文名称
每个 logger 记录器都附加到一个记录器上下文。默认情况下,它的名称为 “default”。
通过 < contextName> 配置可以更改其名称,使用此值打印到日志中,用于区分不同应用程序的记录。但一旦设置,它的名称就无法变更。
<configuration>
<contextName>myAppName</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
9、变量定义和替换
9.1、介绍
logback 配置文件支持变量的定义和替换。
变量具有作用域。
变量可以在配置文件中,在外部文件中,在外部资源中,甚至可以即时计算和定义。
变量替换可以发生在配置文件中可以指定值的任何位置。语法是 ${variableName}。
考虑到常用性,HOSTNAME 和 CONTEXT_NAME 变量默认已定义,并具有上下文作用域。考虑到在某些环境中可能需要花费一些时间来计算主机名,因此它的值是延迟计算的(仅在需要时)。
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} ${HOSTNAME} ${CONTEXT_NAME} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
变量可以在 logback 自己的配置文件中定义,也可以从外部属性文件或外部资源中批量加载。由于历史原因,用于定义变量用 。
<configuration>
<property name="LOG_HOME" value="./logs"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
它下面的效果一样,logback 将在 System 属性中查找它。
java -LOG_HOME="./logs" MyApp
如果你定义的变量太多时,可以创建单独的文件来保存,方便管理。
<configuration>
<property file="src/main/java/resources/logback-variables.properties"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
此配置会读取logback-variables.properties文件中的变量,然后在本地范围内使用。logback-variables.properties文件定义的变量如下:
LOG_HOME=./logs
当然,也可以写成引入类路径上的资源文件的形式。
<configuration>
<property resource="logback-variables.properties"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
9.2、变量作用域
9.2.1、变量作用域
- Local Scope(本地作用域)
- 从配置文件中定义的本地变量即在本地配置文件使用。每次解析和执行配置文件时,都会重新定义本地作用域中的变量。
- Context Scope(上下文作用域)
- 一个拥有上下文作用域的变量存在于上下文中,于上下文共存,直到被清除。在所有记录事件中都可用到,包括那些通过序列化发送到远程主机的事件。
- System Scope(系统级作用域)
- 系统级作用域的变量被插入到JVM的系统属性中,生命周期和JVM一致,直到被清除。
- 默认是本地作用域。从操作系统环境中读取变量很容易,但是无法写入到操作系统环境中。
在进行属性替换时,查找变量的顺序为:local scope,context scope,system
<configuration>
<property scope="context" name="LOG_HOME" value="./logs"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
9.2.2、引入变量时,可能变量未定义或者值为null,我们可以使用":-"符号指定默认值。例如
${LOG_HOME:-./logs}。
9.2.3、支持变量嵌套,默认值和值定义都可以引用其他变量。例如:
LOG_HOME=./logs
FILE_NAME=myApp.log
destination=${LOG_HOME}/${FILE_NAME}
9.2.4、名称嵌套
引用变量时,变量名称可能包含对另一个变量的引用。例如
如果为名为" userid"的变量分配了值" alice",则" $ {$ {userid} .password}"引用名称为" alice.password"的变量。
9.2.5、默认值嵌套
变量的默认值可以引用另一个变量。例如
假设未分配变量" id",并且为变量" userid"分配了值" alice",则表达式" $ {id :- $ {userid}}"将返回" alice"。
9.2.6、有条件地处理配置文件
我们可能需要在不同的环境(例如dev,test,prod)切换不同的logback配置文件。然而这些配置文件大部分内容是一样的,极少内容是不同的。为了减少多个配置文件,可以使用条件处理标签,< if>, < then> 和 < else>,根据不同环境进行配置。不过,需要引入Janino 库。
<!-- if-then 形式 -->
<if condition="表达式">
<then>
...
</then>
</if>
<!-- if-then-else 形式 -->
<if condition="表达式">
<then>
...
</then>
<else>
...
</else>
</if>
condition 条件只能是上下文属性或系统属性的Java表达式。对于通过参数传递的键,可以通过 property() 或简写的 p() 方法返回属性的字符串值。例如,property(“k”) 或 p(“k”) 访问键" k"的值。如果键" k"的属性未定义,则属性方法将返回空字符串,而不是null。这能避免判断null值。
isDefined()方法可用于检查是否定义了属性。例如,isDefined(“k”) 。
如果需要检查属性是否为null,则可以使用 isNull() 方法。例如,isNull(“k”)。
<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} - %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>
在范围内都可以使用条件处理语句。还支持嵌套的if-then-else语句。但是,XML语法非常繁琐,为以后后续其他开发者以及自己能快速理解,尽量少用。
9.2.7、文件包含
Joran支持将配置文件的一部分包含在另一个文件中。这是通过声明一个 include元素来完成的,如下所示:
可以通过标签< include>来引入另一个配置文件。
<configuration>
<include file="src/main/java/resources/includedConfig.xml"/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
includedConfig.xml文件定义了被引用的内容:
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
标签引入的文件可以为一个文件,一个类路径上的资源,或者一个URL。如下:
<include file="src/main/java/resources/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://xxx.com/includedConfig.xml"/>
如果被引用的文件不存在,logback会打印内部的状态信息。如果包含的文件是可选的,可以通过optional属性设置为true来进制打印显示警告信息。
<include optional="true" ..../>