`

深入源码之SLF4J

    博客分类:
  • java
阅读更多

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倍以上(官网资料,我本人还没有仔细看过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-<version>.jarslf4j-log4j12-<version>.jarslf4j-jdk14-<version>.jarlogback-classic-<version>.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()方法的主要源码如下:

 private final static void 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);

      throw new 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实例:

 public static ILoggerFactory getILoggerFactory() {

    if (INITIALIZATION_STATE == UNINITIALIZED) {

      INITIALIZATION_STATE = ONGOING_INITIALIZATION;

      performInitialization();

    }

    switch (INITIALIZATION_STATE) {

      case SUCCESSFUL_INITIALIZATION:

        return StaticLoggerBinder.getSingleton().getLoggerFactory();

      case NOP_FALLBACK_INITIALIZATION:

        return NOP_FALLBACK_FACTORY;

      case FAILED_INITIALIZATION:

        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);

      case ONGOING_INITIALIZATION:

        // support re-entrant behavior.

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

        return TEMP_FACTORY;

    }

    throw new 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-<version>.jar。要实现一个兼容SLF4J的日志系统,基本的需要三个类:

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

public class StaticLoggerBinder implements LoggerFactoryBinder {

 private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

 public static final StaticLoggerBinder getSingleton() {

    return SINGLETON;

 }

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

 public static String REQUESTED_API_VERSION = "1.6.99"// !final

 private static final String loggerFactoryClassStr = SimpleLoggerFactory.class.getName();

 private final ILoggerFactory loggerFactory;

 private StaticLoggerBinder() {

    loggerFactory = new SimpleLoggerFactory();

 }

 public ILoggerFactory getLoggerFactory() {

    return loggerFactory;

 }

 public String getLoggerFactoryClassStr() {

    return loggerFactoryClassStr;

 }  

}

2.    实现ILoggerFactory接口的SimpleLoggerFactory。它有一个loggerMap字段缓存所有之前创建的SimpleLogger实例,以Logger Namekey,实现每个相同名字的Logger实例只需要创建一次。

public class SimpleLoggerFactory implements ILoggerFactory {

 final static 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.<logNamePrefix>

并且所有这些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-<version>.jarStaticMarkerBinder返回BasicMarkerFactory实例,而StaticMDCBinder返回NOPMDCAdapter实例。

其他桥接包

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

SLF4JCommons LoggingLog4J之间的相互转化

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

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

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

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

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

本文转载自:http://www.blogjava.net/DLevin/archive/2012/11/08/390991.html

分享到:
评论

相关推荐

    slf4j日志框架的源代码分享

    SLF4J(Simple Logging...深入研究SLF4J源码可以帮助我们理解其工作原理,更好地利用这个强大的日志门面。对于想要学习日志管理、软件设计模式或想了解如何编写简洁易用的API的开发者来说,SLF4J源码是一个宝贵的资源。

    slf4j-log4j12-1.7.5.jar的源码

    总的来说,`slf4j-log4j12-1.7.5.jar`的源码提供了对SLF4J和Log4j交互的深入洞察,对于日志处理的优化、问题排查和日志框架的选择具有重要参考价值。通过学习和研究这些源码,开发者可以提升自己的日志管理能力,...

    调试日志之slf4j+logback

    本文将深入探讨“调试日志之slf4j+logback”的主题,这两个组件是Java开发中的常见日志解决方案。 SLF4J(Simple Logging Facade for Java)是一个日志门面,它为各种日志框架提供了统一的接口,如Log4j、Logback等...

    Using slf4j with logback

    SLF4J(Simple Logging Facade for Java)是Java日志框架的一个抽象层,它提供了一个统一的API,...通过深入研究和实践`slf4j-example`项目,你将能够更熟练地掌握这种日志机制,从而提升你的开发效率和日志管理能力。

    slf4j-1.7.16.zip

    这个`slf4j-1.7.16.zip`文件包含的是SLF4J的1.7.16版本的源码、库文件和其他相关资源。 **1. SLF4J介绍** SLF4J的主要目标是允许最终用户在部署时插入所需的日志框架。SLF4J提供了一组API,使得开发者可以在不关心...

    slf4j 1.5.0 源码

    SLF4J 1.5.0是该框架的一个版本,包含了源码,方便开发者深入理解和定制。 源码分析: 1. **接口设计**:SLF4J的核心接口是`org.slf4j.Logger`,它定义了日志的基本操作,如`trace()`, `debug()`, `info()`, `warn...

    slf4j库文件(含sources,不含javadoc)

    SLF4J,全称Simple Logging Facade for Java,是一个为各种日志框架提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的日志框架。它并不实际做日志记录,而是作为一个适配器,允许开发者选择...

    slf4j-1.6.1.rar

    SLF4J的1.6.1版本是一个重要的发布,包含了文档、源码和jar包,方便开发者深入理解和使用这个库。 首先,让我们详细了解SLF4J的基本概念。SLF4J的主要目标是为各种日志框架(如Log4j、Logback等)提供一个统一的...

    slf4j+logback

    这些源码可以帮助开发者深入理解SLF4J和Logback的工作原理,同时也便于调试和定制。 在实际应用中,使用SLF4J+Logback可以实现灵活的日志管理。例如,可以通过以下步骤配置Logback: 1. 创建`logback.xml`配置文件...

    slf4j-1.5.8.rar Hibernate日志接口

    5. **Source Code**:如果包含源码,开发者可以查看内部实现,更深入地学习SLF4J的工作原理。 在Hibernate中,日志接口通常会使用SLF4J。为了配置Hibernate使用SLF4J,你需要在Hibernate的配置文件(如`hibernate....

    slf4j中的MDC

    SLF4J(Simple Logging Facade for Java)是Java中一个简单日志门面的接口,它为各种日志框架提供了统一的API,比如Logback、Log4j等。MDC,全称Mapped Diagnostic Context,是SLF4J提供的一种上下文诊断功能,用于...

    log4j-1.2.15.jar+shiro-all-1.3.2.jar+slf4j-api-1.6.1.jar+slf4j-log4j12-1.6.1.jar

    在给出的标题和描述中,我们看到了Shiro项目所需的几个基础组件,它们是:log4j-1.2.15.jar、shiro-all-1.3.2.jar、slf4j-api-1.6.1.jar以及slf4j-log4j12-1.6.1.jar。这些jar文件在Java应用的构建和运行过程中扮演...

    log4j logback slf4j

    本文将深入探讨三个重要的Java日志框架——Log4j、Logback和SLF4J,并分析它们之间的关系和使用场景。 **Log4j** Log4j是Apache组织的一个开源项目,是Java世界中最先广泛使用的日志记录框架之一。它的核心功能...

    Logback+Slf4j,基于SpringBoot实现日志脱敏.zip

    本文将深入探讨如何利用Logback和Slf4j在SpringBoot项目中实现日志的敏感信息脱敏,以保护用户隐私,满足合规性需求。 首先,我们需要理解什么是日志脱敏。日志脱敏是指在记录日志时,对敏感信息如身份证号、手机号...

    日志框架统一输出控制(slf4j+log4j)

    本文将深入探讨如何通过SLF4J作为日志接口,结合Log4j作为具体的日志实现,实现日志框架的统一输出控制。 首先,SLF4J是一个日志门面,提供了一组API,允许开发者在不关心具体日志实现的情况下编写日志代码。它的...

    slf4j+logback 于log4j+commons-logging大PK

    SLF4J通过`slf4j-api.jar`提供API,而具体的日志实现则通过相应的桥接器(如`log4j-over-slf4j.jar`)来实现。 Logback则是由Log4j的创始人Ceki Gülcü设计的一个更现代、更高效的日志系统。它被设计为比Log4j更快...

    基于Java语言的slf4j日志框架设计源码分析与应用

    本项目深入分析了基于Java语言的SLF4J日志框架设计源码,涉及357个文件,涵盖259个Java源文件、23个XML配置文件、18个JAR包文件、17个文本文件、13个HTML文件、9个属性文件、4个YAML文件以及少量其他类型文件。...

    涵盖SpringBoot的AOP、Slf4j日志门面-springboot-in-action.zip

    本教程将深入探讨SpringBoot中的两个重要概念:AOP(面向切面编程)和Slf4j日志门面。 首先,让我们来理解AOP。AOP在Spring中是一种强大的设计模式,它允许程序员定义“切面”,这些切面可以封装横切关注点,如事务...

    log4j学习源码教程

    本教程将通过源码分析,深入讲解log4j的工作原理和使用方法。 **1. log4j的基本概念** - **Logger**: 日志记录器,是log4j的核心接口,用于生成不同级别的日志事件。 - **Level**: 日志级别,包括DEBUG、INFO、...

    log4j-2.15.0-rc2

    《深入理解log4j-2.15.0-rc2:SLF4J漏洞修复与源码解析》 在IT行业中,日志记录扮演着至关重要的角色,它帮助开发者追踪程序运行状态,定位错误,优化性能。Log4j作为Java世界中最广泛使用的日志框架之一,其最新...

Global site tag (gtag.js) - Google Analytics