日志框架的加载导致冲突问题的原因
经常使用slf4j-api接口包时引入log4j包,总是出现下列的错误,网上查找了一圈,都是说实现包冲突,几乎都是官网的答案。比较少有分析为什么会报这个错的原因。主要有两大类问题:1.引入了slf4j-api的包,但没有引入它的实现包,2.引入了slf4j-api的包,但是实现类引入了多个。
未引入sfl4j-api包的实现包
报错Failed to load class "org.slf4j.impl.StaticLoggerBinder"
,使用NOP实现,其实就是空调用,打印不出日志。
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.
解决的方法也比较简单:
引入slf4j-api的实现,slf4j的实现包主要有slf4j-simple
,slf4j-log4j12
,对于这两个实现包,可以在这个网站搜索:https://mvnrepository.com/,如果是maven项目则很方便配置,如果不是则选择下载的链接下载下来。这两个实现包引入其中一个即可。
引入多个sfl4j-api包的实现包
如果引入了多个slf4j-api的实现包,则会报错:SLF4J: Class path contains multiple SLF4J bindings.
,但一般slf4j会自行取其中一个实现进行打印。但最好保证只有一个实现包。
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/Maven/repository/org/slf4j/slf4j-log4j12/1.7.27/slf4j-log4j12-1.7.27.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/Maven/repository/org/slf4j/slf4j-simple/1.7.28/slf4j-simple-1.7.28.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.Log4jLoggerFactory]
有时候我们的项目引入的包很复杂,有些日志包是传递以来引入的,导致很难找到具体的引入,如果是maven项目那么可以使用mvn dependency:tree > tree.txt
将项目的依赖导入到文件中,然后我们可以搜索slf4j或者log
字样,找到引入的日志包,进而判断是否重复了,是否冲突了。如下使用mvn dependency:tree
:
有了这个依赖树,很方便就可以找到那个日志依赖引入冲突了,从而可以将多引入的日志实现去掉。
slf4j-api查找依赖源码分析
直接看org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
的实现,LoggerFactory
是slf4j-api
包中的一个类,它也是slf4j日志的入口,通过getLogger(Class<?> clazz)
获取Logger实例,打印日志。
public static Logger getLogger(Class<?> clazz) {
//<1>获取日志实例
Logger logger = getLogger(clazz.getName());
//是否检测日志的名字不匹配,其实是clazz类是否在这个打印日志的地方的堆栈中存在的,默认false
if (DETECT_LOGGER_NAME_MISMATCH) {
//<2> 获取调用堆栈的类,并且去除slf-api中的类。保证打印的是应用的堆栈类的信息。
Class<?> autoComputedCallingClass = Util.getCallingClass();
//<3>如果clazz不在堆栈中则打印提示信息
if (nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
}
}
return logger;
}
getLogger
方法,会调用org.slf4j.LoggerFactory#getILoggerFactory
public static ILoggerFactory getILoggerFactory() {
//<1> 判断是否初始化过,如果已经初始化过Logger则INITTIALZATION_STATE为0
if (INITIALIZATION_STATE == 0) {
INITIALIZATION_STATE = 1;
//<2> 否则进行初始化,核心!!!
performInitialization();
}
switch(INITIALIZATION_STATE) {
case 1:
//初始化过了,返回已经初始化factory
return TEMP_FACTORY;
case 2:
//初始化失败
throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. 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");
}
}
performInitialization()
往里面走最终会走到bind()方法,这里决定了INITIALIZATION_STATE
,代码如下所示:
private static final void bind() {
String msg;
try {
//<1> 查找"org/slf4j/impl/StaticLoggerBinder.class"的实现类,可能存在多个所以是Set
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//<2> 如果有多个,则提示有多个实现。
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
//<3> 获取实际的实现StaticLoggerBinder,这个其实是在实现包中的,如果没有引入实现,是会抛异常的
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = 3;//初始化状态为3,单例
//<4> 打印具体的实现
reportActualBinding(staticLoggerBinderPathSet);
fixSubstitutedLoggers();
} catch (NoClassDefFoundError var2) {
msg = var2.getMessage();
if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
//没找到"org/slf4j/impl/StaticLoggerBinder.class"实现类
failedBinding(var2);
throw var2;
}
INITIALIZATION_STATE = 4;
//<5>未加载到实现类,对应了第一种情况,可能没有引入实现包。
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} catch (NoSuchMethodError var3) {
msg = var3.getMessage();
if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
INITIALIZATION_STATE = 2;
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 var3;
} catch (Exception var4) {
failedBinding(var4);
throw new IllegalStateException("Unexpected initialization failure", var4);
}
}
- 查找"org/slf4j/impl/StaticLoggerBinder.class"的实现类
- 如果有多个,则提示有多个实现。
- 获取实际的实现StaticLoggerBinder,这个其实是在实现包中的,如果没有引入实现,是会抛异常的。
- 异常情况的处理和判断,更新INITIALIZATION_STATE。
reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet)
里主要是提示有多个实现类,并且选择第一个实现类作为实现。
private static void reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet) {
if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
Util.report("Class path contains multiple SLF4J bindings.");
Iterator iterator = staticLoggerBinderPathSet.iterator();
while(iterator.hasNext()) {
URL path = (URL)iterator.next();
Util.report("Found binding in [" + path + "]");
}
Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
}
}