slf4j日志加载原理分析

780 阅读3分钟

引言

前不久log4j2因为一个漏洞闹的沸沸扬扬,一天N个应用要升级版本发布,有些系统不动它还好,一动它就是各种问题。反正日志就是不打印……最后费了好久一顿乱操作(各种尝试)最后终于可以打印日志了(其实也不知道是为啥)!暗自下决心得把这个搞懂。不知大家是否有过这样的经历

日志体系

这是目前java应用的常用日志体系结构也不是太全。

只聊一下slf4j和我们平时常用的一些日志框架 log4j1.x、log4j2.x、logback

slf4j-api:简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案。是为各种日志提供一个简单统一的接口,从而让用户在项目中引入各自的日志实现框架

这是官网上的一个设计图

image.png

源码分析

我们通常在引用日志的时候是使用了这个方法

Logger logger = LoggerFactory.getLogger(LogTest.class);

下面我们走进去看一下吧,还是比较简单的,首先slf4j-api包中并没有日志绑定类【org.slf4j.impl.StaticLoggerBinder】其实是在打包时候做排掉了。由其它各框架自己实现绑定类返回其工厂的实现

进入方法查看

//获取日志对象
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
}
//获取日志工厂
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                   //初始化日志工厂,这里走进入会有日志的绑定操作
                    performInitialization();
                }
            }
        }

        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
            //已经绑定成功返回日志的工厂
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
}

org.slf4j.LoggerFactory#bind
 //日志框架的绑定
 private static final void bind() {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                //这里是去找org.slf4j.impl.StaticLoggerBinder的包
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                //下面是打印日志,发现的日志框架
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            //这里是拿到具体的实现框架,由于可能是加载多个,但是由于JVM的双亲委托加载机制
            //这里只会加载一个,具体是哪种这个需要看jvm加载的顺序了
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = 3;
            //这里会打印出实际使用了哪个日志
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            SUBST_FACTORY.clear();
}

这里在我的项目中引入了上面列出的3个日志框架

项目在启动时则会打印出

SLF4J: Class path contains multiple SLF4J bindings.
 //log4j2
SLF4J: Found binding in [jar:file:/D:/Maven_Repositry/org/apache/logging/log4j/log4j-slf4j-impl/2.14.1/log4j-slf4j-impl-2.14.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
//logback
SLF4J: Found binding in [jar:file:/D:/Maven_Repositry/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
//log4j1
SLF4J: Found binding in [jar:file:/D:/Maven_Repositry/org/slf4j/slf4j-log4j12/1.6.1/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
 //实际绑定的是log4j2
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]

一句话说就是通过类StaticLoggerBinder的加载来确定到底使用哪个日志框架。如果项目中没有配置【log4j2.xml】的日志配置而配置的是【logback】的配置则会导致项目的日志出现不能打印的情况。

下面我们列一下每种框架所需的jar吧

log4j1.x:一般情况需要,slf4j-api、slf4j-log4j12(桥接包)、log4j

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
</dependency>       
<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
</dependency>
<dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.16</version>
 </dependency>

log4j2.x:一般情况需要,slf4j-api、log4j-slf4j-impl(桥接包)、log4j-core、log4j-api(版本要在2.15.0以上因为之前有bug)

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
</dependency>
<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.14.1</version>
</dependency>
<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.15.0</version>
</dependency>
<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.15.0</version>
</dependency>

logback:只需要引入 logback-classic 就好了

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
</dependency>
<dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
</dependency>

现在弄清楚这个关系在来查问题就很容易定位了。