log4j與logback包衝突緣由及解決,不可忽視的Warning

場景

一個簡單的spring-boot程序,須要用kafka作消息隊列,因而在maven中引入kafka依賴,一切看似沒問題,在啓動時,打印出Warning信息:html

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衝突。java

  • 疑惑點1是我並無手動引入slf4j-log4j12依賴,依賴jar包是被自動引入的,經過maven自帶工具分析依賴路徑,能夠看出是kafka依賴於slf4j-log4j12,自動導入的依賴包。
  • 日誌綁定的機制分析 從日誌對象開始探究slf4j的綁定方式。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
···
private final Logger logs = LoggerFactory.getLogger(***.class);

LoggerFactory.getLogger()方法:(下述均只保留關鍵邏輯代碼 )spring

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()的真面目:apache

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()是進行初始化的方法:api

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>返回。maven

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()方法中:spring-boot

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的目錄結構供參考: ui

解決方案

分析了緣由,那麼解決方案天然很簡單,就是剔除不須要的依賴包,此處就是在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>

本文就簡單分析了日誌加載綁定的過程,若有遺漏請不吝指出。

相關文章
相關標籤/搜索