logback:
logback可以认为是log4j的升级版,依然出自Ceki Gülcü,使用简单,只需要在你的classpath里包含slf4j-api.jar、logback-core.jar以及logback-classic.jar即可。
简单代码示例如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
public class LogTest {
public static void main(String[] args) {
Logger logger = LoggerFactory
.getLogger(LogTest .class.getName());
logger.debug("Hello world.");
}
其中的logger对象和LoggerFactory都来自slf4j项目,slf4j是一个很好的facade,包装了接口,就像之前写的一篇文章中的提到的commons-logging框架。
同样也可以认为logback+slf4j是log4j+commons-logging的升级版吧。
logback的架构:
logback包含3个子工程——classic、core和access。core是其他两个的基础,也是logback的核心;classic扩展了core,内置了slf4j,但也支持各种其他log门面。同log4j一样,logback的主要构成也是Logger、Appender和Layout。Logger是核心控制器及调用入口,Appender主管配置和写日志实际process,Layout控制日志样式,是Appender的重要配置。值得注意的是,3个基本核心居然不都在core模块中,Logger是在classic里的。
对于Logger来讲,和Log4j一样,Logger是一个层次结构,每个logger都有一个name属性在LoggerFactory中被一个map管理着。对于Logback来说,这个factory就是classic下的LoggerContext。这里插一段自我理解,在做facade模式的时候,代码结构可能会引入一个占位性质的类,就像slf4j中的StaticLoggerBinder,这个类在org.slf4j.impl包下,是一个单例,但是私有构造函数却抛出了一个异常,这个在不熟悉这种写法时会产生困惑。其实这是很合理的,logback的classic中也有org.slf4j.impl这个包,其中也有StaticLoggerBinder这个类,但是内容完整了许多。这就完成了slf4j的任务,同时解除了耦合。我认为这种解耦合方式非常好,plugin的感觉。
再回来说LoggerContext,这个对应了log4j的LogManager和Hierarchy,用一个hashtable来维护logger的cache。代码真的是简洁了很多,再回头看看log4j中LogManager的getLogger方法,就知道logback的简洁了,一个while遍历省去了一个hierarchy。当然这里得补充一句,log4j包括logback的整个日志框架对于logger对象,是一个层次结构,这也是为什么log4j中有个Hierarchy的东西的原因。是一个层次的话,对于通过包名来管理日志记录等级的管理方式来说,就存在着level的控制,也就是说,你某个包名的类被设定了日志级别是什么,那么对应级别以下的日志才会被打印出来。有这样一个规则,logger的日志记录有效level由hierarchy中离它最近且方向向上(upwards)的一个logger的级别决定。如官网上的例子:
这里有4个logger,但是只有root被设定了level是DEBUG,其他几个logger由于没有被设定,依照规则,就都是root的level了。
第二个例子中,每个logger都自己设定了level,那么依据规则,离它最近的被使用,当然自己离自己最近了。
第三个例子里,X.Y没有设定level,那么离它最近且upwards的一个是X,那么X.Y的level就和X的一样。
level控制是日志框架的基础,什么样的日志在什么环境下被打印出来,这种设定可以配置才是一个合理的日志系统。一贯的level控制规则如下显示:
appender这个东西,和log4j是一样的,支持一个logger有多个appender,在AppenderAttachableImpl里会维护一个CopyOnWriteArrayList来存放一个logger的appender。每次log的时候都会遍历这个list里的appender然后调用对应的doAppend方法。我们在配置的时候每个logger的配置上有个additivity属性,默认为true。appender这个东西同logger一样有继承性。additivity属性就是控制这种继承的,true代表开启,false代表关闭,一般使用都会设置false,因为如果是true,那么如果appender比较多的话可能日志打的就有点太离谱了。
Layout和log4j一样,我没有细研究过,但是我认为这是控制输出的一大法宝,下次研究layout的时候做一个详细的分享,一般大家都使用patternLayout,写个表达式足以。
在之前的那篇写log的文章中,提到过一个优化写法,就是不要直接写出这样的代码:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
构造string参数是复杂且耗时的。要用if判断一下。而slf4j提供了比较合适的解决方法:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
这样的代码就更符合程序设计人员的编写习惯,而且可读性我认为要远远高于用+号连接。但是遗憾的是,这种编码风格作者并没有持续贯彻下去,没有用变长参数,而是用的object[]终止了参数的个数。也就是说,你对于多个变量的log的话,只能这么写:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
在执行log的时候,会有一个判断流程,依据官网上的介绍,我简要翻译一下:
1,Get the filter chain decision:如果存在,尝试调用TurboFilter
,TurboFilter
会设置一个整个上下文范围的阈值或者过滤掉每个log请求的相关参数。如果这个filter返回的参数是FilterReply.DENY
,那么log结束;如果FilterReply.NEUTRAL
返回,则进入第2步,如果FilterReply.ACCEPT ,直接第3步。
2, Apply the basic selection rule,如果log请求的阈值和高于配置的阈值,那么放弃处理该log。
3, Create a LoggingEvent
object,logback会构建一个LoggingEvent对象,包含了所有的请求参数。其中有些参数可能是延迟加载的。
4, Invoking appenders,logback调用doAppend方法。
5, Formatting the output,layout会把LoggingEvent对象按固定格式格式化并返回字符串形式,像SocketAppender这样的方法不会返回字符串,相似的只会把它序列化。
6, Sending out the LoggingEvent,把最终形式打印到对应的目的地址。
流程图见这里http://logback.qos.ch/manual/underTheHood.html
最后还是通过性能讨论结束这篇短文,性能的东西,我们不去看代码的话,是无法估计复杂度的变化的。那么就官网上给出的3条提示,第一点针对参数构建,第二点针对level的定位,这个在看过代码后,发现确实精简了,尤其是那种复杂的hierarchy结构没有了,线性的链条运行起来明显会快,算是去除冗余做了优化吧。第3条针对说format和write会快,尤其是format被大力投入改进,这个在看过代码后,可以做个比较。
本文算是一篇半自主半翻译的文章吧,重在学习。
参考文献:
http://logback.qos.ch/manual/introduction.html