专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

slf4j日志框架是如何实现的

为什么要用slf4j

市面上的日志框架实在太多了!log4j,logback,java.util.logging,log4j2,将来也可能会有其他新的更优秀的日志框,你也可能根据公司的需求自己开发一套日志组件。 那么问题来了,如果我用log4j,来到了一个项目组用的是logback,那是不是又要从看一遍API文档开始了(虽然大部分的日志框架的用法都大同小异)。但无形中就增加了学习成本。

slf4j就是解决这样的问题的。

什么是slf4j

slf4j其实是抽象出来的一套日志接口,定义的是一个统一的规范,事实上,它自身不提供具体实现。 建一个空项目,pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>logtest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

</project>

测试类:

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLog {
    @Test
    public void testLog() {
        Logger logger = LoggerFactory.getLogger(TestLog.class);
        logger.error("Yan");
    }
}

运行测试方法testLog(),报错如下:

45_1.png显然,提示没有找到日志的具体实现。这就印证了上面的说法! 我们随便加上一个日志实现,pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>logtest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <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>
    </dependencies>

</project>

再次运行测试用例

45_2.png正常了

让我们再来试试多个日志实现框架一起

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>logtest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <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>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
    </dependencies>
</project>

运行结果如下:

45_3.png日志框架加载的时候,提示发现了多个实现层,但是最终选定了logback作为具体实现,并且正确输出了日志。

这种设计模式叫做门面模式,好处就是,我们无论实现层用的是什么日志框架,我们在使用的时候,都是用的org.slf4j.Logger.java接口中的方法,而屏蔽了不同日志框架的差异性,省去了不必要的学习成本。

slf4j是如何做到这些的

我们从测试方法中的 LoggerFactory.getLogger(TestLog.class) 点进去一观究竟

45_4.png

45_5.png

45_6.png

45_7.png进入到bind()方法,就是我们的重头戏了:

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

先看第7行的findPossibleStaticLoggerBinderPathSet()方法,这里将获取前面多次提到的StaticLoggerBinder,也就是slf4j和真正的日志实现之间连接的纽带,由于不一定只引入一种日志实现框架,因此获取的是一个Set集合。点进去看一下

45_8.png这里其实就是用类加载器去获取classpath下的 org/slf4j/impl/StaticLoggerBinder.class,而这个class只有在logback等具体实现框架中有,因此,如果只引入slf4j,这里获得Set就是空的,并且在bind()方法走到 StaticLoggerBinder.getSingleton() 这一步的时候抛出 NoClassDefFoundError,并报出

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

45_9.png

而如果引入了多个日志实现框架,在 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet) 这一步将打印出如下信息:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/72037717/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/72037717/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/72037717/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

在接下来的**reportActualBinding(staticLoggerBinderPathSet)**中将会确定真正使用的日志实现框架

45_10.png这一步其实是让编译器来决定选择哪一个StaticLoggerBinder,这里只是打印出结果而已。 此时我们需要做一件事情,那就是决定以哪个日志实现框架来继续往下研究到底是如何取得Logger对象,进而实现后期的打印日志的功能的。因为performInitialization()往后的事情就和具体日志实现不可分割了。(从严格意义上来讲,这已经超出了本文标题所指的讨论范围了,但我还是决定刨根问底)。 我这里选定的是log4j日志框架,这是我在工作中最常用的框架。

log4j在slf4j中的具体实现

log4j和logback不太一样。由于apache log4j本身并没有考虑去兼容实现slf4j框架,所以slf4j-log4j12这个包就作为适配器出现连接了两者。这是典型的适配器模式

45_11.png

45_12.png可以看出来 步骤a返回的ILoggerFactory的运行时类是Log4jLoggerFactory。 来到更上一层

45_13.png

45_14.png步骤d可以观察到 apache的log4j中也有一个Logger类,但我们最终获得Logger类其实是slf4j中的,运行时类是Log4jLoggerAdapter,这个类你可以看作是org.apache.log4j.Logger的适配器,后续我们在使用org.slf4j.Logger.java的info(),error()等方法时,它会传递调用org.apache.log4j.Logger的info(),error()方法。是不是真相大白了?接下来的就是把org.slf4j.Logger一直抛至测试业务调用层了。

文章永久链接:https://tech.souyunku.com/31487

未经允许不得转载:搜云库技术团队 » slf4j日志框架是如何实现的

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们