Change Dir

先知cd——热爱生活是一切艺术的开始

统计

留言簿(18)

积分与排名

“牛”们的博客

各个公司技术

我的链接

淘宝技术

阅读排行榜

评论排行榜

logback那些事

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的级别决定。如官网上的例子:

image

这里有4个logger,但是只有root被设定了level是DEBUG,其他几个logger由于没有被设定,依照规则,就都是root的level了。

image

第二个例子中,每个logger都自己设定了level,那么依据规则,离它最近的被使用,当然自己离自己最近了。

image

第三个例子里,X.Y没有设定level,那么离它最近且upwards的一个是X,那么X.Y的level就和X的一样。

level控制是日志框架的基础,什么样的日志在什么环境下被打印出来,这种设定可以配置才是一个合理的日志系统。一贯的level控制规则如下显示:

image

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:如果存在,尝试调用TurboFilterTurboFilter 会设置一个整个上下文范围的阈值或者过滤掉每个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

posted on 2012-03-31 17:14 changedi 阅读(2894) 评论(1)  编辑  收藏 所属分类: Java技术


只有注册用户登录后才能发表评论。


网站导航: