slf4j绑定学习

一、导读

我们使用log4j框架时,经常会用slf4j-api。在运行时,经常会遇到如下的错误提示:

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/slf4j/slf4j-simple/1.7.26/slf4j-simple-1.7.26.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]

也即classpath下有多个日志实现,slf4j-api在绑定时出现了冲突。那么这时slf4j-api到底会绑定哪一个具体的实现呢?

官方文档有如下说明:

源码解读SLF4J绑定日志实现的原理-LMLPHP

 

 

源码解读SLF4J绑定日志实现的原理-LMLPHP

也就是说,当有多个日志实现时,SLF4J会在编译期随机选择其中的一个实现。那么,它到底是如何随机选择的呢?下面我们通过源码来分析。

请尊重作者劳动成果,转载请标明原文链接:https://www.cnblogs.com/waterystone/p/11329645.html

二、源码分析

2.1 LoggerFactory.getLogger(this.getClass())

我们通过LoggerFactory.getLogger(this.getClass())拿到Logger对象,其代码如下:

public static Logger getLogger(Class<?> clazz) {

    Logger logger = getLogger(clazz.getName());

    if (DETECT_LOGGER_NAME_MISMATCH) {

        Class<?> autoComputedCallingClass = Util.getCallingClass();

        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {

            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),

                            autoComputedCallingClass.getName()));

            Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");

        }

    }

    return logger;

}

  

2.2 LoggerFactory.bind()

getLogger() -> getILoggerFactory()-> performInitialization() -> bind() ,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(); //在classpath下查找有多少个日志实现

            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //如果有多个日志实现,打印出来

        }

        // the next line does the binding。classpath下每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类,这里会随机加载其中的一个。

        StaticLoggerBinder.getSingleton();

        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;

        reportActualBinding(staticLoggerBinderPathSet);

        fixSubstituteLoggers();

        replayEvents();

        // release all resources in SUBST_FACTORY

        SUBST_FACTORY.clear();

    } catch (NoClassDefFoundError ncde) { //如果在classpath下没有找到一个org.slf4j.impl.StaticLoggerBinder

        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);

    }

}

  

2.3 LoggerFactory.findPossibleStaticLoggerBinderPathSet()

接下来,我们再看看findPossibleStaticLoggerBinderPathSet()如何在classpath下查找有多少个日志实现的源码:

static Set<URL> findPossibleStaticLoggerBinderPathSet() {

    // use Set instead of list in order to deal with bug #138

    // LinkedHashSet appropriate here because it preserves insertion order

    // during iteration

    Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();

    try {

        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();

        Enumeration<URL> paths;

        if (loggerFactoryClassLoader == null) { //用ClassLoader去查找classpath下有多少个org.slf4j.impl.StaticLoggerBinder类

            paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);

        } else {

            paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);

        }

        while (paths.hasMoreElements()) {

            URL path = paths.nextElement();

            staticLoggerBinderPathSet.add(path);

        }

    } catch (IOException ioe) {

        Util.report("Error getting resources from path", ioe);

    }

    return staticLoggerBinderPathSet;

}

  

通过源码我们可以看到,findPossibleStaticLoggerBinderPathSet()会用ClassLoader查找当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,每个path的URL能定位到其具体jar包位置。每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类,反过来,那么classpath下有多少个StaticLoggerBinder类,就会有多少个相应的jar包,也即有多少个日志实现。所以findPossibleStaticLoggerBinderPathSet()通过扫描classpath下的org.slf4j.impl.StaticLoggerBinder类就能找到有多少个日志实现

接下来,我们再看看是不是每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类呢?答案是YES

三、具体日志实现

3.1 slf4j-simple

源码解读SLF4J绑定日志实现的原理-LMLPHP

源码解读SLF4J绑定日志实现的原理-LMLPHP

3.2 slf4j-nop

3.3 logback-classic

3.4 log4j-slf4j-impl

源码解读SLF4J绑定日志实现的原理-LMLPHP

四、总结

  • 每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类的实现;
  • SLF4J会通过ClassLoad扫描当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,也就找到了有多少个日志实现(通过这样,我们只需在项目中加入日志实现的jar包,编译时即可自动加载,业务代码无须显式依赖,实现解耦);
  • 如果有多个org.slf4j.impl.StaticLoggerBinder类,SLF4J会在LoggerFactory.bind()里调用StaticLoggerBinder.getSingleton()随机加载一个日志实现jar的StaticLoggerBinder。
  • 发现一个有趣的现象,在本地idea上运行时,是加载pom.xml里声明的第一个日志实现;但是如果打包好后通过java -jar启动时,其加载的日志实现确实是随机的(是在编译打包时随机加载一个日志实现,所以一旦编译打包好后其加载的那个日志实现就会固定不变)。所以,我们在具体使用时一定要通过排除依赖的方式来确定日志实现,不要由于日志实现的不确定性引入难以排查、不必要的坑。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值