log4j与logback包冲突原因及解决,不可忽视的Warning

场景

一个简单的spring-boot程序,需要用kafka做消息队列,于是在maven中引入kafka依赖,一切看似没问题,在启动时,打印出Warning信息:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

原因分析

通过警告消息,可以简单的看出是slf4j绑定发生问题,有多个StaticLoggerBinder.class存在,即slf4j-log4j12logback-classic冲突。

  • 疑惑点1是我并没有手动引入slf4j-log4j12依赖,依赖jar包是被自动引入的,通过maven自带工具分析依赖路径,可以看出是kafka依赖于slf4j-log4j12,自动导入的依赖包。
    1026668-20181107203403792-1374584466.png
    1026668-20181107203344894-768839611.png
  • 日志绑定的机制分析
    从日志对象开始探究slf4j的绑定方式。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
···
private final Logger logs = LoggerFactory.getLogger(***.class);

LoggerFactory.getLogger()方法:(下述均只保留关键逻辑代码 )

public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        ···
        return logger;
}
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();//看这里
        return iLoggerFactory.getLogger(name);//根据名字返回一个Logger实例对象
}

ILoggerFactory是一个接口,归属package org.slf4j;仅存在一个方法为:
public Logger getLogger(String name);
接下来就是看看getILoggerFactory()的真面目:

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            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://jira.qos.ch/browse/SLF4J-97
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

可以看到performInitialization()是进行初始化的方法:

private final static void performInitialization() {
    bind();
    ···
}

performInitialization()内部调用bind()方法:

private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        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) {...
    } catch (java.lang.NoSuchMethodError nsme) {...
    } catch (Exception e) {...}
}

其中关键在于findPossibleStaticLoggerBinderPathSet()方法,终于到了查找绑定相关的部分内容,可以看到是查找所有的"org/slf4j/impl/StaticLoggerBinder.class"类并加载,同时while循环里,将可能存在的多个StaticLoggerBinder.class路径均加入Set<URL>返回。

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration<URL> paths;
        if (loggerFactoryClassLoader == null) {//看这里
            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;
}

返回到bind()方法中:

private final static void bind() {
    ···
    staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//看这里
    ···
}
···
private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
    if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
        Util.report("Class path contains multiple SLF4J bindings.");
        for (URL path : binderPathSet) {
            Util.report("Found binding in [" + path + "]");
        }
        Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
    }
}

这里可以看到reportMultipleBindingAmbiguity()里判断是否发生多重绑定,就是打印文章开头Warning信息的地方。
成功加载StaticLoggerBinder后,在bind()方法中调用其getSingleton()方法得到单例,并修改INITIALIZATION_STATE状态,至此完成日志框架的绑定。

private final static void bind() {
    if (!isAndroid()) {
        staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
    }
    StaticLoggerBinder.getSingleton();//看这里
    INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
    reportActualBinding(staticLoggerBinderPathSet);
    ···
}

最后附上slf4j-api-1.7.25.jarlogback-classic-1.2.3.jar的目录结构供参考:
1026668-20181107204118939-1673290995.png1026668-20181107204136161-866933930.png

解决方案

分析了原因,那么解决方案自然很简单,就是剔除不需要的依赖包,此处就是在kafka的依赖中剔除slf4j-log4j12maven项目中可以通过exclusions标签来完成。

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_2.11</artifactId>
    <version>0.10.0.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

本文就简单分析了日志加载绑定的过程,如有遗漏请不吝指出。

转载于:https://www.cnblogs.com/LeeMouRen/p/9925541.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值