要在项目中引入日志,需要一定的代价,据统计日志代码量将会占整个代码量的4%左右。因此我们需要一个工具管理这些日志声明。
对于Log4j.properties可以通过以下网址转化成logback.xml
http://logback.qos.ch/translator/
Logback如何查找配置文件:
- 在classpath中查找logback.groovy
- 如果不存在,则在classpath中查找logback-test.xml
- 如果不存在,继续查找logback.xml
- 如果不存在,则由jdk1.6中提出的service-provider loading facility 通过查找META-INF\services\ch.qos.logback.classic.spi.Configurator这个文件中定义的继承
com.qos.logback.classic.spi.Configurator接口的配置实现类的全限定名
- 如果还不存在,logback则会使用基本的配置,将日志输出定向到控制台。
如果你使用maven构建项目,可以将logback-test.xml放在src/test/resources路径下,如此,该配置就只在test阶段会启用。将logback.xml放在src/main/resources中,则会随项目一起打包到正式环境使用。
快速启动(不明觉厉):将配置文件转化需要100毫秒的时间,可以通过使用以上第四步的方法来缩减这个时间。
我们来看看上面第5步的BasicConfigurator的源码
public class BasicConfigurator extends ContextAwareBase implements Configurator {
31
32 public BasicConfigurator() {
33 }
34
35 public void configure(LoggerContext lc) {
36 addInfo("Setting up default configuration.");
37
38 ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender<ILoggingEvent>();
39 ca.setContext(lc);
40 ca.setName("console");
41 LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
42 encoder.setContext(lc);
43
44
45 // same as
46 // PatternLayout layout = new PatternLayout();
47 // layout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
48 TTLLLayout layout = new TTLLLayout();
49
50 layout.setContext(lc);
51 layout.start();
52 encoder.setLayout(layout);
53
54 ca.setEncoder(encoder);
55 ca.start();
56
57 Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
58 rootLogger.addAppender(ca);
59 }
60 }
这个最小的配置为root logger绑定一个ConsoleAppender,输出则使用PatternLayoutEncoder将日志模板设置成
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n.
另外,root logger的默认级别是debug。
输出格式如下:
16:06:09.031 [main] INFO chapters.configuration.MyApp1 - Entering application. 16:06:09.046 [main] DEBUG chapters.configuration.Foo - Did it again! 16:06:09.046 [main] INFO chapters.configuration.MyApp1 - Exiting 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.");
}
}
例子中我们使用slf4j的接口类,并没有使用直接导入logback的实现。事实上,我们也不需要让logback客户端声明式地依赖logback,因为slf4j会在抽象层采用可以使用的日志框架。这就为我们可以随时方便地切换日志框架提供了可能。
打印logback status message(logback上下文状态)的方法
方式一:
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);
...
}
方式二:cofiguration 的debug设置为true,但如果该配置文件有错将无法自动输出状态信息。
<configuration debug="true">
这种方式也相当于,注册了一个statusListener
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
</configuration>
我们也可以通过jvm参数指定默认配置文件,注意:文件名必须以.xml或者.groovy结尾
java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1
自动重载配置:当配置文件更新时,自动重新加载
扫描时间间隔单位可以是:milliseconds, seconds, minutes or hours.,如不设置默认为1分钟检测一次。实际上扫描间隔,不单单取决于间隔时间,还取决于logger request的次数
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
允许记录产生错误时的堆栈状态,默认为false。虽然有用,但在错误频繁的系统中,性能消耗比较大
<configuration packagingData="true">
...
</configuration>
也可以通过代码实现
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);
如果你想改写logback默认的配置加载机制,可以调用 JoranConfigurator.doConfigure(String arg0)重新配置
但是先要调用loggerContext.context的reset()方法清除之前的配置
public static void main(String[] args) {
// assume SLF4J is bound to logback in the current environment
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
}
通过网页查看LoggerContext上下文信息:Logback将它的内部状态数据手机在StatusManager中,可以通过context获取。
可以在web.xml中引入以下servlet,然后就可以通过对应的url访问如下界面:
<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>
设置日志状态监听器:注册一个状态监听器,可以方便管理logback的内部状态,在没有人工参与的情况下。
代码实现:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusManager statusManager = lc.getStatusManager();
OnConsoleStatusListener onConsoleListener = new OnConsoleStatusListener();
statusManager.add(onConsoleListener);
配置实现(推荐):状态监听器的声明应该在一开始就定义,否则无法收集到声明之前的状态操作
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
... the rest of the configuration file
</configuration>
jvm参数实现:
java -Dlogback.statusListenerClass=ch.qos.logback.core.status.NopStatusListener ...
logback提供了一个状态监听器的实现
OnConsoleStatusListener:输出到控制台
OnErrorConsoleStatusListener:输出到错误控制台
NopStatusListener:丢弃状态信息
在某些情况下,为了释放logback带来的资源消耗,可以暂停logback context
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
在web应用中,以上功能页可以通过调用servletContextListener的contextDestroyed方法实现,原话如下:
In web-applications the above code could be invoked from within the contextDestroyed method
of ServletContextListener
in order to stop logback-classic and release resources.
下面我们来说说Configuration文件的编写格式
这是一个基本的结构图
首先看看他的一些基本规范
- 对大小写敏感(建议采用全小写):虽然大小写不明感,但是开/闭标签需要大小写一致,第一个字母除外。
- logger通过<logger>标签定义,属性如下:
- name:必要,唯一标识一个Logger
- level:可选,不设置默认从父logger中继承,logger日志级别,可选值:TRACE, DEBUG, INFO, WARN, ERROR, ALL or OFF
- additivity:可选,默认为true,表示是否叠加祖宗logger的appender, appender代表一个日志输出地
- <appender-ref>:内部元素,至少包含一个,属性ref=“appender”的name。
- <root>:只提供一个level属性,因为name=ROOT,additity=false已经规定。
以下是一个例子:
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO" />
<logger name="chapters.configuration.Foo" level="DEBUG" />
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
下面我们再来看看怎么定义<appender>
需要注意是:logger默认的additity为true,代表同时向祖宗logger的appender输出,所以错误设置,可能导致重复的日志记录
如果有需要,这时候就需要声明additity=false
<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>
设置context的name:目的是区别当有多个应用程序logging到同一个目的,如何区分来自哪个application
<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>
定义属性:可以通过定义属性,同一管理一些重复设置的值,获取方式类似于EL表达式,表达式之间可以嵌套。
<configuration>
<property name="USER_HOME" value="/home/sebastien" />
<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>
当然也可以通过jvm参数设置
java -DUSER_HOME="/home/sebastien" MyApp2
如果属性较多,也可以引入文件系统中单独的属性文件,或classpath路径下的文件
<configuration>
<!--文件系统-->
<property file="src/main/java/chapters/configuration/variables1.properties" />
<!--类路径-->
<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>
定义的属性的范围:
- LOCAL
- CONTEXT
- SYSTEM
<property scope="context" name="nodeId" value="firstNode" />
获取属性值的时候,也可以默认值的方式,当属性未定义,则采用默认值
${aName:-golden}
${id:-${userid}}
如果属性值使用过计算获得,也可以动态定义,实现PropertyDefiner接口的getPropertyValue()
<configuration>
<define name="rootLevel" class="a.class.implementing.PropertyDefiner">
<shape>round</shape>
<color>brown</color>
<size>24</size>
</define>
<root level="${rootLevel}"/>
</configuration>
logback也提供了两个简单的动态定义属性类
Implementation name | Description |
---|---|
FileExistsPropertyDefiner |
Set the named variable to "true" if the file specified by path property exists, to "false" otherwise. |
ResourceExistsPropertyDefiner |
Set the named variable to "true" if the resource specified by the user is available on the class path, to "false" otherwise. |
当然,有时候我们也需要根据情况适当调整配置:需要注意的是,p("k")相当于property("k"),且当属性未定义,或null时,都同一返回空字符串。这样就可以减少对属性的判断操作
<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>
我们也可以从JNDI中获取属性值,as 代表属性引入后的名字
<configuration>
<insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" />
<contextName>${appName}</contextName>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d ${CONTEXT_NAME} %level %msg %logger{50}%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
我们也可以将配置文件分片,然后引入,optional属性代表非必须
<configuration>
<include file="src/main/java/chapters/configuration/includedConfig.xml" optional=false/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
文件名:src/main/java/chapters/configuration/includedConfig.xml
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
引入路径也分为三种
- file system: <include file=
- classpath:<include resource=
- url:<include url=