天天看點

slf4j和log4j、logback的關系一、slf4j和log4j、logback的關系二、slf4j的使用三、代碼層面看slf4j和log4j、logback的關系四、假設在classpath下既提供了log4j的實作,又提供了logback的實作,那麼slf4j具體使用的是哪種日志?

一、slf4j和log4j、logback的關系

slf4j(simple logging facade for java)簡單日志門面,隻是為各種日志實作提供了日志使用的接口,并沒有具體的日志實作。

log4j和logback是具體的日志實作,不使用slf4j我們也可以直接使用log4j或者logback實作日志記錄。slf4j相當于是各種日志接口的接口,調用的時候,直接使用slf4j的日志接口,具體的日志實作隻需要通過提供不同的日志實作的jar和配置檔案,就可以靈活的實作日志的切換,對于應用代碼無感覺。

通過一張圖看看slf4j和log4j、logback的關系

slf4j和log4j、logback的關系一、slf4j和log4j、logback的關系二、slf4j的使用三、代碼層面看slf4j和log4j、logback的關系四、假設在classpath下既提供了log4j的實作,又提供了logback的實作,那麼slf4j具體使用的是哪種日志?

可以看出來slf4j和apache的common-log的功能是一緻的,為各種日志實作提供一個統一的通路接口,門面模式。

二、slf4j的使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {

    private static final Logger log = LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        for (;;) {
            log.info("hello {}", "world");
        }
    }

}
           

程式代碼中引用的是slf4j-api-xxx.jar包裡的類,僅僅有slf4j-api-xxx.jar是不夠的,還需要在classpath下提供log4j(slf4j-log4j12-xxx.jar+log4j-xxx.jar)或者logback(logback-classic-xxx.jar+logback-core-xxx.jar)的實作。

三、代碼層面看slf4j和log4j、logback的關系

看看上面例子中的LoggerFactory.getLogger(xxx.class)方法是如何擷取到日志實作的,跟蹤getLogger()方法

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  }
           

首先傳回一個ILoggerFactory(已經是具體的實作了),然後擷取Logger。繼續跟進getILoggerFactory()方法

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
      INITIALIZATION_STATE = ONGOING_INITILIZATION;
      performInitialization();

    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITILIZATION:
      return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITILIZATION:
      return NOP_FALLBACK_FACTORY;
    case FAILED_INITILIZATION:
      throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITILIZATION:
      // support re-entrant behavior.
      // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
      return TEMP_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
  }
           

如果還沒初始化就先初始化,如果初始化成功就通過 StaticLoggerBinder的執行個體傳回LoggerFactory。看下初始化方法performInitialization()方法

private final static void performInitialization() {
    singleImplementationSanityCheck();
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITILIZATION) {
      versionSanityCheck();
   
    }
  }
           

檢查classpath下所有org/slf4j/impl/StaticLoggerBinder.class資源,然後bind()。看下檢查和綁定的過程

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

  private static void singleImplementationSanityCheck() {
    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);
      }
      List implementationList = new ArrayList();
      while (paths.hasMoreElements()) {
        URL path = (URL) paths.nextElement();
        implementationList.add(path);
      }
      if (implementationList.size() > 1) {
        Util.report("Class path contains multiple SLF4J bindings.");
        for (int i = 0; i < implementationList.size(); i++) {
          Util.report("Found binding in [" + implementationList.get(i) + "]");
        }
        Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
      }
    } catch (IOException ioe) {
      Util.report("Error getting resources from path", ioe);
    }
  }
           

org/slf4j/impl/StaticLoggerBinder.class有什麼特殊的嗎?

特殊之處在于它并不是在slf4j-api-xxx.jar中,它是在log4j的日志擴充卡(slf4j-log4j12-xxx.jar)或者是logback的日志擴充卡(logback-classic-xxx.jar)中,每種日志實作的jar裡面都會提供一個包路徑和類名稱全都相同的StaticLoggerBinder類。再看一眼bind()方法

private final static void bind() {
    try {
      // the next line does the binding
      StaticLoggerBinder.getSingleton();
      INITIALIZATION_STATE = SUCCESSFUL_INITILIZATION;
      emitSubstituteLoggerWarning();
    } catch (NoClassDefFoundError ncde) {
      String msg = ncde.getMessage();
      if (msg != null && msg.indexOf("org/slf4j/impl/StaticLoggerBinder") != -1) {
        INITIALIZATION_STATE = NOP_FALLBACK_INITILIZATION;
        Util
            .report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
        Util.report("Defaulting to no-operation (NOP) logger implementation");
        Util.report("See " + NO_STATICLOGGERBINDER_URL
            + " for further details.");
      } else {
        failedBinding(ncde);
        throw ncde;
      }
    } catch(java.lang.NoSuchMethodError nsme) {
      String msg = nsme.getMessage();
      if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
        INITIALIZATION_STATE = FAILED_INITILIZATION;
        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. or 2.0.x");
      }
      throw nsme;
    } catch (Exception e) {
      failedBinding(e);
      throw new IllegalStateException("Unexpected initialization failure", e);
    }
  }
           

關鍵的地方是紅色标注的,通過StaticLoggerBinder.getSingleton()實作綁定,這個類的執行個體要麼是log4j提供的jar裡面的StaticLoggerBinder類生成的對象,要麼是logback提供的jar裡面的StaticLoggerBinder類生成的對象。slf4j官方推薦隻能放一種日志實作到classpath中,那也就是程式中真正使用的日志實作。slf4j怎麼擷取到一種具體的日志也就明了了。

四、假設在classpath下既提供了log4j的實作,又提供了logback的實作,那麼slf4j具體使用的是哪種日志?

這個問題,涉及到了java的類加載機制,這又稍微複雜點。簡單說下java類加載機制就是:雙親委派機制。在不重寫自定義的classloader的情況下,Java虛拟機預設提供的是啟動類加載器(bootstrap class loader)、擴充類加載器(extensions class loader)、系統類加載器(system class loader),也是三層父子關系,使用者還可以實作自定義的類加載器。

slf4j和log4j、logback的關系一、slf4j和log4j、logback的關系二、slf4j的使用三、代碼層面看slf4j和log4j、logback的關系四、假設在classpath下既提供了log4j的實作,又提供了logback的實作,那麼slf4j具體使用的是哪種日志?

不同的類加載器負責加載特定路徑的類,如下圖:

slf4j和log4j、logback的關系一、slf4j和log4j、logback的關系二、slf4j的使用三、代碼層面看slf4j和log4j、logback的關系四、假設在classpath下既提供了log4j的實作,又提供了logback的實作,那麼slf4j具體使用的是哪種日志?

當目前類加載器需要加載一個類的時候,先通路父類加載器有沒有,一直通路到啟動類加載器,如果沒有加載,目前類加載器才去加載這個類。而classpath下面的類是由系統類加載器加載的。系統類加載器會按照類在classpath中的次序加載類,對于相同包路徑相同名字的類,類加載器隻會加載classpath第一次出現的那個,至于classpath後面再出現同路徑同名的類,classloader會忽略,不會再加載。

是以,如果classpath下即提供了log4j的實作,也提供了logback的實作,那麼看它們(slf4j-log4j12-xxx.jar、logback-classic-xxx.jar)在classpath下出現的次序,誰在前面,slf4j就使用誰來實作日志記錄。

下面的一個小工具可以判斷classpath下的一個class是從哪個jar裡面加載的

public class JWhich {

    /**
     * Prints the absolute pathname of the class file containing the specified class name, 
     * as prescribed by the current classpath.
     * @param className Name of the class.
     */
    public static void which(String className) {

        if (!className.startsWith("/")) {
            className = "/" + className;
        }
        className = className.replace('.', '/');
        className = className + ".class";

        java.net.URL classUrl = new JWhich().getClass().getResource(className);

        if (classUrl != null) {
            System.out.println("\nClass '" + className + "' found in \n'" + classUrl.getFile() + "'");
        } else {
            System.out.println("\nClass '" + className + "' not found in \n'" + System.getProperty("java.class.path") + "'");
        }
    }

    public static void main(String args[]) {
        if (args.length > 0) {
            JWhich.which(args[0]);
        } else {
            System.err.println("Usage: java JWhich <classname>");
        }
    }
}
           

用法 java -classpath classpath JWhich com.xxx.XXX.class