slf4j和log4j、logback的关系

from:slf4j和log4j、logback的关系

一、slf4j和log4j、logback的关系


slf4j(simple logging facade for Java)简单日志门面,只是为各种日志实现提供了日志使用的接口,并没有具体的日志实现。

log4j和logback是具体的日志实现,不使用slf4j我们也可以直接使用log4j或者logback实现日志记录。slf4j相当于是各种日志接口的接口,调用的时候,直接使用slf4j的日志接口,具体的日志实现只需要通过提供不同的日志实现的jar和配置文件,就可以灵活的实现日志的切换,对于应用代码无感知。

通过一张图看看slf4j和log4j、logback的关系





可以看出来slf4j和apache的common-log的功能是一致的,为各种日志实现提供一个统一的访问接口,门面模式。

二、slf4j的使用




  1. import org.slf4j.Logger;

  2. import org.slf4j.LoggerFactory;


  3. public class Test {


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


  5.     public static void main(String[] args) {

  6.         for (;;) {

  7.             log.info("hello {}""world");

  8.         }

  9.     }

  10. }
程序代码中引用的是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()方法
  1. public static Logger getLogger(String name) {

  2.     ILoggerFactory iLoggerFactory = getILoggerFactory();

  3.     return iLoggerFactory.getLogger(name);

  4.   }

首先返回一个ILoggerFactory(已经是具体的实现了),然后获取Logger。继续跟进getILoggerFactory()方法
  1. public static ILoggerFactory getILoggerFactory() {

  2.     if (INITIALIZATION_STATE == UNINITIALIZED) {

  3.       INITIALIZATION_STATE = ONGOING_INITILIZATION;

  4.       performInitialization();


  5.     }

  6.     switch (INITIALIZATION_STATE) {

  7.     case SUCCESSFUL_INITILIZATION:

  8.       return StaticLoggerBinder.getSingleton().getLoggerFactory();

  9.     case NOP_FALLBACK_INITILIZATION:

  10.       return NOP_FALLBACK_FACTORY;

  11.     case FAILED_INITILIZATION:

  12.       throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);

  13.     case ONGOING_INITILIZATION:

  14.       // support re-entrant behavior.

  15.       // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106

  16.       return TEMP_FACTORY;

  17.     }

  18.     throw new IllegalStateException("Unreachable code");

  19.   }
如果还没初始化就先初始化,如果初始化成功就通过StaticLoggerBinder的实例返回LoggerFactory。看下初始化方法performInitialization()方法
  1. private final static void performInitialization() {

  2.     singleImplementationSanityCheck();

  3.     bind();

  4.     if (INITIALIZATION_STATE == SUCCESSFUL_INITILIZATION) {

  5.       versionSanityCheck();
  6.     }

  7.   }

检查classpath下所有org/slf4j/impl/StaticLoggerBinder.class资源,然后bind()。看下检查和绑定的过程
  1. private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";


  2.  private static void singleImplementationSanityCheck() {

  3.    try {

  4.      ClassLoader loggerFactoryClassLoader = LoggerFactory.class

  5.          .getClassLoader();

  6.      Enumeration paths;

  7.      if (loggerFactoryClassLoader == null) {

  8.        paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);

  9.      } else {

  10.        paths = loggerFactoryClassLoader

  11.            .getResources(STATIC_LOGGER_BINDER_PATH);

  12.      }

  13.      List implementationList = new ArrayList();

  14.      while (paths.hasMoreElements()) {

  15.        URL path = (URL) paths.nextElement();

  16.        implementationList.add(path);

  17.      }

  18.      if (implementationList.size() > 1) {

  19.        Util.report("Class path contains multiple SLF4J bindings.");

  20.        for (int i = 0; i < implementationList.size(); i++) {

  21.          Util.report("Found binding in [" + implementationList.get(i) + "]");

  22.        }

  23.        Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");

  24.      }

  25.    } catch (IOException ioe) {

  26.      Util.report("Error getting resources from path", ioe);

  27.    }

  28.  }

org/slf4j/impl/StaticLoggerBinder.class有什么特殊的吗?

特殊之处在于它并不是在slf4j-api-xxx.jar中,它是在log4j的日志适配器(slf4j-log4j12-xxx.jar)或者是logback的日志适配器(logback-classic-xxx.jar)中,每种日志实现的jar里面都会提供一个包路径和类名称全都相同的StaticLoggerBinder类。再看一眼bind()方法
  1. private final static void bind() {

  2.     try {

  3.       // the next line does the binding

  4.       <span style="color:#ff0000;">StaticLoggerBinder.getSingleton();</span>

  5.       INITIALIZATION_STATE = SUCCESSFUL_INITILIZATION;

  6.       emitSubstituteLoggerWarning();

  7.     } catch (NoClassDefFoundError ncde) {

  8.       String msg = ncde.getMessage();

  9.       if (msg != null && msg.indexOf("org/slf4j/impl/StaticLoggerBinder") != -1) {

  10.         INITIALIZATION_STATE = NOP_FALLBACK_INITILIZATION;

  11.         Util

  12.             .report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");

  13.         Util.report("Defaulting to no-operation (NOP) logger implementation");

  14.         Util.report("See " + NO_STATICLOGGERBINDER_URL

  15.             + " for further details.");

  16.       } else {

  17.         failedBinding(ncde);

  18.         throw ncde;

  19.       }

  20.     } catch(java.lang.NoSuchMethodError nsme) {

  21.       String msg = nsme.getMessage();

  22.       if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {

  23.         INITIALIZATION_STATE = FAILED_INITILIZATION;

  24.         Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");

  25.         Util.report("Your binding is version 1.5.5 or earlier.");

  26.         Util.report("Upgrade your binding to version 1.6.x. or 2.0.x");

  27.       }

  28.       throw nsme;

  29.     } catch (Exception e) {

  30.       failedBinding(e);

  31.       throw new IllegalStateException("Unexpected initialization failure", e);

  32.     }

  33.   }

关键的地方是红色标注的,通过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
),也是三层父子关系,用户还可以实现自定义的类加载器。



不同的类加载器负责加载特定路径的类,如下图:



当当前类加载器需要加载一个类的时候,先访问父类加载器有没有,一直访问到启动类加载器,如果没有加载,当前类加载器才去加载这个类。而classpath下面的类是由系统类加载器加载的。系统类加载器会按照类在classpath中的次序加载类,对于相同包路径相同名字的类,类加载器只会加载classpath第一次出现的那个,至于classpath后面再出现同路径同名的类,classloader会忽略,不会再加载。

所以,如果classpath下即提供了log4j的实现,也提供了logback的实现,那么看它们(slf4j-log4j12-xxx.jar、logback-classic-xxx.jar)在classpath下出现的次序,谁在前面,slf4j就使用谁来实现日志记录。

下面的一个小工具可以判断classpath下的一个class是从哪个jar里面加载的
  1. public class JWhich {


  2.     /**

  3.      * Prints the absolute pathname of the class file containing the specified class name, 

  4.      * as prescribed by the current classpath.

  5.      * @param className Name of the class.

  6.      */

  7.     public static void which(String className) {


  8.         if (!className.startsWith("/")) {

  9.             className = "/" + className;

  10.         }

  11.         className = className.replace('.''/');

  12.         className = className + ".class";


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


  14.         if (classUrl != null) {

  15.             System.out.println("\nClass '" + className + "' found in \n'" + classUrl.getFile() + "'");

  16.         } else {

  17.             System.out.println("\nClass '" + className + "' not found in \n'" + System.getProperty("java.class.path") + "'");

  18.         }

  19.     }


  20.     public static void main(String args[]) {

  21.         if (args.length > 0) {

  22.             JWhich.which(args[0]);

  23.         } else {

  24.             System.err.println("Usage: java JWhich <classname>");

  25.         }

  26.     }

  27. }

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