以前用過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);
}
}