上善若水
in general the oo style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. to do is to be -nietzsche, to bei is to do -kant, do be do be do -sinatra
posts - 146,comments - 147,trackbacks - 0

commons logging log4j一直是java日志的经典组合,以至于很多服务器都使用了类似的配置,像websphere、以前的tomcat都使用commons logging作为日志输出框架,而据说jboss则直接commons logginglog4j一起使用了(这个估计是为了解决commons logging中经常在这类服务器上遇到的classloader问题)。然而log4j的开发团队对commons logging貌似不满意(可以从log4j manual中看出一些端倪),因而log4j团队开发了自己的日志门面框架slf4jsimple logging façade for java),貌似他们对自己开发的log4j的性能也不满意,然后又弄出了个logback,关键执行语句的性能要比log4j10倍以上(凯发k8网页登录官网资料,我本人还没有仔细看过logback的代码,更没有测试过,不知道具体性能能提高多少),这是后话,等过几年看logback代码后再仔细讨论。

以我个人理解,slf4j的出现是为了解决commons logging存在的两个问题:

1.    commons logging存在的classloader问题,即当服务器本身引入commons logging时,如果在服务器中载入commons logging包,则该包中的类由服务器的classloader加载,从而在加载log4j中的logger类时会出现classnotfoundexception,因为服务器中的classloader没法找到web app下的jar包。对于父classloader优先的类加载机制来说,目前的一个凯发天生赢家一触即发官网的解决方案是使用commons-logging-api-1.1.1.jar包,该包的log实现类只包含jdk14loggersimplelognooplog三个类,因而在加载这几个类时会使用web app中的commons logging包,从而解决classnotfoundexception的问题,然而这种方式对那些实现child classloader first的服务器来说,由webclassloaderclassloader加载的类在使用日志时会存在问题,因为它们的log接口由加载自身类的classloader加载,而log4jlogger类却由webclassloader加载。具体关于commons logging中存在的问题我会在另外一篇文章中详细说明。

2.    在使用commons logging时,我们经常会看到以下方法的写法:

if (logger.isdebugenabled()) {

    logger.info("loading xml bean definitions from " encodedresource.getresource());

}

存在isdebugenabled()的判断逻辑是为了在避免多余的字符串拼接,即如果不存在isdebugenabled()判断,即使当前日志级别为error时,在遇到logger.info()调用时,它还会先拼接日志消息的字符串,然后进入该方法内,才发现这个日志语句不用打印。而这种多余的拼接不仅浪费了多余的cpu操作,而且会增加gc的负担。slf4j则提供以下的方式来解决这个问题:

logger.info("loading xml bean definitions from {}", encodedresource.getresource());

 

slf4j综述

类似commons loggingslf4j在使用时通过loggerfactory得到命名的logger实例,然后通过该logger实例调用相应的方法打印日志:

final logger logger = loggerfactory.getlogger("levin.logging.slf4j");

logger.info("using slf4j, current time is {}"new date());

然而不同于commons logging的动态绑定机制,slf4j则采用了一种静态绑定的机制,即每个支持slf4jlogging框架必须存在一个继承自loggerfactorybinder接口的staticloggerbinder类:

public interface loggerfactorybinder {

 public iloggerfactory getloggerfactory();

   public string getloggerfactoryclassstr();

}

loggerfactory调用staticloggerbinder类中的getloggerfactory()方法返回相应的iloggerfactory实例:

public interface iloggerfactory {

 public logger getlogger(string name);

}

最后通过iloggerfactory实例获取logger实例:

public interface logger {

   public string getname();

   ...(trace)

   public boolean isdebugenabled();

   public void debug(string msg);

   public void debug(string format, object arg);

   public void debug(string format, object arg1, object arg2);

   public void debug(string format, object... arguments);

   public void debug(string msg, throwable t);

   public boolean isdebugenabled(marker marker);

   public void debug(marker marker, string msg);

   public void debug(marker marker, string format, object arg);

   public void debug(marker marker, string format, object arg1, object arg2);

   public void debug(marker marker, string format, object... arguments);

   public void debug(marker marker, string msg, throwable t);

   ...(info)

   ...(warn)

   ...(error)

}

也正是因为这个设计,slf4jclasspath下只支持一个桥接包(slf4j-simple-.jarslf4j-log4j12-.jarslf4j-jdk14-.jarlogback-classic-.jar等)。如果在classpath下存在多个桥接包,则具体用哪个就要看这几个桥接包的加载顺序了,实际中会使用先加载的桥接包。同时slf4j会打印使用哪个桥接包,哪些桥接包没有使用。这种静态绑定的设计比commons logging在可扩展性上具有更加灵活的机制,对“可插拔”的支持也更加高效。如果要支持一个新的logging框架,commons logging需要通过在属性配置文件、或虚拟机属性中配置支持这个新的logging框架的实现类(实现log接口);而slf4j则只需要编写一个五个相应的类:

1.    实现logger接口的类

2.    实现iloggerfactory接口的类

3.    实现loggerfactorybinder接口的类staticloggerbinder类(必须使用staticloggerbinder类名),并且存在一个静态的getsingleton()方法。

4.    实现markerfactorybinder类的staticmarkerbinder类(必须使用staticmarkerbinder类名),可选。一般也会存在一个静态的singleton字段,不过也是可选的。

5.    实现staticmdcbinder类,可选。一般也会存在一个静态的singleton字段,也可选。

slf4j的类设计也相对比较简单(也感觉有点零散):


slf4j实现实例,slf4j apislf4j simple

由于采用了静态绑定的方式,而不是像commons logging中的动态绑定,slf4jloggerfactory的实现要比commons logginglogfactory的实现要简单的多。即loggerfactory调用getiloggerfactory()方法,该方法会初始化loggerfactory,即通过在bind()方法中加载classpath中的staticloggerbinder类,并根据加载结果设置当前loggerfactory的初始化状态,从而在getiloggerfactory()方法中通过当前loggerfactory的状态判断返回的iloggerfactory实例。简单的示意图如下:


bind()方法的主要源码如下:

 privatefinalstaticvoid bind() {

    try {

      ...

      //实现绑定

      staticloggerbinder.getsingleton();

      initialization_state = successful_initialization;

      ...

    } catch (noclassdeffounderror ncde) {

      string msg = ncde.getmessage();

      //判断是否是因为没有找到staticloggerbinder类引起的异常

      //此时,使用noploggerfactory类返回给getiloggerfactory(),不打印任何日志

      if (messagecontainsorgslf4jimplstaticloggerbinder(msg)) {

        initialization_state = nop_fallback_initialization;

        ...

      } else {

        // initialization_state = failed_initialization

        failedbinding(ncde);

        throw ncde;

      }

    } catch (java.lang.nosuchmethoderror nsme) {

      string msg = nsme.getmessage();

      if (msg != null && msg.indexof("org.slf4j.impl.staticloggerbinder.getsingleton()") != -1) {

        initialization_state = failed_initialization;

        ...

      }

      throw nsme;

    } catch (exception e) {

      //initialization_state = failed_initialization;

      failedbinding(e);

      thrownew illegalstateexception("unexpected initialization failure", e);

    }

 }

bind()方法使用调用staticloggerbinder.getsingleton()方法来实现绑定,如果该方法调用成功,则将初始化状态设置为successful_initialization,如果因为没有找到staticloggerbinder类而引起的异常,则将状态设置为nop_fallback_initialization,否则将状态设置为failed_initialization,并抛出异常。如果在当前classpath下存在多个桥接jar包,在实现绑定前后会记录存在哪些可使用的桥接jar包,绑定了那个iloggerfactory类。

bind()返回后,performinitialization()方法会再做一些版本检查,即staticloggerbinder可以定义一个静态的requested_api_version字段,表示该staticloggerbinder支持的slf4j版本,如果该版本不在loggerfactory定义的兼容版本列表中(api_compatibility_list),slf4j会打印警告信息,并列出当前loggerfactory兼容的版本列表。而后在getiloggerfactory()方法中会根据当前loggerfactory的初始化状态来决定返回的iloggerfactory实例:

 publicstatic iloggerfactory getiloggerfactory() {

    if (initialization_state == uninitialized) {

      initialization_state = ongoing_initialization;

      performinitialization();

    }

    switch (initialization_state) {

      casesuccessful_initialization:

        return staticloggerbinder.getsingleton().getloggerfactory();

      casenop_fallback_initialization:

        return nop_fallback_factory;

      casefailed_initialization:

        throw new illegalstateexception(unsuccessful_init_msg);

      caseongoing_initialization:

        // support re-entrant behavior.

        // see also http://bugzilla.slf4j.org/show_bug.cgi?id=106

        return temp_factory;

    }

    thrownew illegalstateexception("unreachable code");

 }

loggerfactory成功初始化,则返回绑定的staticloggerbinder中的iloggerfactory实例;如果为nop_fallback_initialization(没有找到桥接jar),则返回noploggerfactory,它返回一个单例的noplogger实例,该类不会打印任何日志;如果初始化状态为failed_initialization,抛出illegalstateexception异常;如果初始化状态为ongoing_initialization,则返回substituteloggerfactory类实例,该状态发生在一个线程正在初始化loggerfactory,而另一个线程已经开始请求获取iloggerfactory实例,substituteloggerfactory会记录当前请求的logger名称,然后返回noplogger实例。所有这些在loggerfactory初始化时被忽略的logger name会在loggerfactory初始化成功以后被report出来(在system.err流中打印出来)。

      slf4j实现了一个简单的日志系统:slf4j-simple-.jar。要实现一个兼容slf4j的日志系统,基本的需要三个类:

1.    staticloggerbinder类,实现loggerfactorybinder接口。它实现单例模式,存在getsingleton()静态方法,存在requested_api_verion静态字段,不用final避免编译器的优化(将值直接写入源码中,而不使用该字段)。返回的iloggerfactory实例也一直使用同一个实例(simpleloggerfactory)。

public class staticloggerbinder implements loggerfactorybinder {

 privatestaticfinal staticloggerbinder singleton = new staticloggerbinder();

 publicstaticfinal staticloggerbinder getsingleton() {

    returnsingleton;

 }

 // to avoid constant folding by the compiler, this field must *not* be final

 publicstatic string requested_api_version = "1.6.99"// !final

 privatestaticfinal string loggerfactoryclassstr = simpleloggerfactory.class.getname();

 privatefinal iloggerfactory loggerfactory;

 private staticloggerbinder() {

    loggerfactory = new simpleloggerfactory();

 }

 public iloggerfactory getloggerfactory() {

    returnloggerfactory;

 }

 public string getloggerfactoryclassstr() {

    returnloggerfactoryclassstr;

 }  

}

2.    实现iloggerfactory接口的simpleloggerfactory。它有一个loggermap字段缓存所有之前创建的simplelogger实例,以logger namekey,实现每个相同名字的logger实例只需要创建一次。

public class simpleloggerfactory implements iloggerfactory {

 finalstatic simpleloggerfactory instance = new simpleloggerfactory();

 map loggermap;

 public simpleloggerfactory() {

    loggermap = new hashmap();

 }

 public logger getlogger(string name) {

    logger slogger = null;

    // protect against concurrent access of the loggermap

    synchronized (this) {

      slogger = (logger) loggermap.get(name);

      if (slogger == null) {

        slogger = new simplelogger(name);

        loggermap.put(name, slogger);

      }

    }

    return slogger;

 }

}

3.    simplelogger类,实现logger接口。simplelogger继承自markerignoringbase类,该基类忽略所有存在marker参数的日志打印方法。simplelogger将日志级别分成五个级别:tracedebuginfowarnerror,这些级别对应的int值一次增大。simplelogger还支持对simplelogger.properties配置文件的解析,它支持的key值有:

org.slf4j.simplelogger.defaultloglevel

org.slf4j.simplelogger.showdatetime

org.slf4j.simplelogger.datetimeformat

org.slf4j.simplelogger.showthreadname

org.slf4j.simplelogger.showlogname

org.slf4j.simplelogger.showshortlogname

org.slf4j.simplelogger.logfile

org.slf4j.simplelogger.levelinbrackets

org.slf4j.simplelogger.warnlevelstringwarn提示字符,默认“warn”)

同时simplelogger还支持为特定的logger name前缀(以”.”作为分隔符)指定level

org.slf4j.simplelogger.log.

并且所有这些key都可以定义在系统属性中。

simplelogger类的实现主要分成两步:初始化和打印日志:

a.    初始化

加载配置文件,使用加载的配置文件初始化类字段,即对应以上simplelogger.properties支持的key;保存当前logger name;计算当前logger实际的level,即如果没有为该logger name(或其以“.”分隔的前缀)配置特定的level,则使用默认配置的level,否则,使用具体的日志,并保存计算出的level值,如果没有找到level配置,使用默认值info

b.    打印日志

对使用format字符串的日志打印方法,调用formatandlog()方法,其内部先调用messageformatter.arrayformat()方法,然后调用log()方法实现打印信息。log()方法的实现只是根据解析出来的配置信息,判断哪些信息需要打印,则打印这些信息,实现比较简单,不再赘述。

markermarkerfactorystaticmarkerbinder以及mdcstaticmdcbinder

并不是所有logging系统支持这些功能,对它们支持最全面的当属logback框架了,因而这些类将会在介绍logback框架时一起讨论。在slf4j-simple-.jarstaticmarkerbinder返回basicmarkerfactory实例,而staticmdcbinder返回nopmdcadapter实例。

其他桥接包

slf4j-log4j12-.jarslf4j-jdk14-.jarslf4j-jcl-.jar等,它们的实现类似slf4j-simple-.jar的实现,并且更加简单,因而它们对logger的实现将大部分的逻辑代理给了底层实现框架,因而这里不再赘述。

slf4jcommons logginglog4j之间的相互转化

slf4j支持上层是slf4j框架,底层还是通过commons logging的动态查找机制,只要将slf4j-jcl-.jar包加入classpath中即可(当然slf4j-api-.jar也要存在)。

另外slf4j还支持上层是commons logging,而底层交给slf4j提供的静态绑定机制查找真正的日志实现框架,只需要将jcl-over-slf4j-.jar包加入到classpath中,此时不需要引入commons-logging-.jar包。它的实现只是重写了commons logging框架,并在logfactory中只使用slf4jlogslf4jlocationawarelog类。

不过需要注意,slf4j-jcl-.jar包和jcl-over-slf4j-.jar两个包不能同时出现在classpath中,不然会引起循环调用而导致栈溢出的问题,因而slf4j-jcl-.jar在初始化时就会检测这个限制,并抛出异常。

最后slf4j还支持log4j作为上层,而底层交给slf4j静态绑定要真正实现日志打印的框架,可以将log4j-over-slf4j-.jar包加入到classpath中。其实现也类似jcl-over-slf4j-.jar的实现,重写大部分的log4j的内部逻辑,而在logger类实现中,将真正的日志打印逻辑代理给slf4jloggerfactory

最后给我现在在公司开发的这个系统吐个槽,我们队日志并没有统一的管理,有人用commons logging,也有人直接用log4j,其实所有代码都没有统一管理,很是换乱,不过slf4j竟然可以满足这种情况的迁移,即可以将log4j-over-slf4j-.jarjcl-over-slf4j-.jar包同时放到classpath下。而到这个时候我才意识到为什么slf4j为什么会慢慢的使用那么广泛了。

posted on 2012-11-08 00:44 dlevin 阅读(13380) 评论(5)     所属分类: logging

feedback:
# re: 深入源码之slf4j
2014-08-14 17:26 |
发送  回复  
  
# re: 深入源码之slf4j
2014-10-09 16:57 |
你好,请教个问题,如果我现在的classpath下有两个桥接包,比如log4j和logback的,有什么办法指定加载某一个吗?我现在每次都是使用log4j。也就是说怎么指定staticloggerbinder这个类的加载呢?  回复  
  
# re: 深入源码之slf4j
2014-11-01 08:52 | dlevin
能想到的一种方法,控制两个桥接包在classpath中的顺序~@zhanjindong
  回复  
  
# re: 深入源码之slf4j
2014-11-01 08:53 | dlevin
不过,你可能要问你自己一个问题,为什么会存在两个桥接包?貌似木有神马意义啊~@zhanjindong
  回复  
  
# re: 深入源码之slf4j
2016-08-14 13:40 |
@zhanjindong
不能指定的,因为在加载的时候他是把所有符合的staticloggerbinder都放在一个set中,然后直接就选择了set集合中第一个加载的类,我想作者考虑的是既然有多个实现都可以满足,就默认使用第一个加载的,因为不可能同时运行多个实现,当然如果你没有实现,程序也是可以运行的,会返回noplogger这个logger的子类,不采取任何处理。不管事多个还是没有具体的实现,都不会影响程序的正常运行。  回复  
  

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


网站导航:
              
 
网站地图