以前用过log4j,只知道简单的使用,是在多人开发的项目中,看到别人使用了,自己才copy过来使用的,但没有深入了解过。前两天开始接触slf4j,据说是一个可以将原有项目中的日志输出框架转换成另外一种新的日志输出框架的第三方开源工具,可以把多个不同项目中的不同日志输出框架通过它转换成同一种输出框架,看了下源码,官网上给了一幅图,如下:
目前它支持过渡的日志框架有jcl、jul和log4j,最终的日志输出框架支持jcl、jul、log4j、logback。
如果你项目中以前是用的log4j作为日志输出框架,想转换成jul,则需要将classpath中原来引用的log4j的jar文件移除,然后添加log4j-over-slf4j-xxx.jar、slf4j-api-xxx.jar、slf4j-jdk14-xxx.jar(xxx指版本号)等三个jar文件即可,项目中的原来的代码都不需要改动,下面简要说一下这三个包的作用:
log4j-over-slf4j-xxx.jar 此包重新实现了log4j.jar包的一些接口,作为桥接器
slf4j-api-xxx.jar 此包是slf4j的api
slf4j-jdk14-xxx.jar 此包是最终选择的日志输出框架
如果原项目中使用的是log4j输出,则使用slf4j桥接之后的最终日志输出框架不能是log4j,源代码中有限制,代码如下:
static { try { Class.forName("org.slf4j.impl.Log4jLoggerFactory"); String part1 = "Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path, preempting StackOverflowError. "; String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details."; Util.report(part1); Util.report(part2); throw new IllegalStateException(part1 + part2); } catch (ClassNotFoundException e) { // this is the good case } }
slf4j是通过静态绑定来确定最终是由哪个框架进行日志输出(每个框架都实现了org/slf4j/impl/StaticLoggerBinder.class),如果出现多个,则会将第一个加载的类作为日志输出,代码如下:
private static Set findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set staticLoggerBinderPathSet = new LinkedHashSet(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class .getClassLoader(); Enumeration paths; if (loggerFactoryClassLoader == null) { paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader .getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) { URL path = (URL) paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; }
它也会报告有哪些被绑定,以及实际被绑定的输出框架
private static void reportMultipleBindingAmbiguity(Set 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 " + MULTIPLE_BINDINGS_URL + " for an explanation."); } } private static void reportActualBinding(Set staticLoggerBinderPathSet) { if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) { Util.report("Actual binding is of type ["+StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr()+"]"); } }
最后会检查版本的兼容性,代码如下:
private final static void versionSanityCheck() { try { String requested = StaticLoggerBinder.REQUESTED_API_VERSION; boolean match = false; for (int i = 0; i < API_COMPATIBILITY_LIST.length; i++) { if (requested.startsWith(API_COMPATIBILITY_LIST[i])) { match = true; } } if (!match) { Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " + Arrays.asList(API_COMPATIBILITY_LIST).toString()); Util.report("See " + VERSION_MISMATCH + " for further details."); } } catch (java.lang.NoSuchFieldError nsfe) { // given our large user base and SLF4J's commitment to backward // compatibility, we cannot cry here. Only for implementations // which willingly declare a REQUESTED_API_VERSION field do we // emit compatibility warnings. } catch (Throwable e) { // we should never reach here Util.report("Unexpected problem occured during version sanity check", e); } }