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的使用

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),也是三层父子关系,用户还可以实现自定义的类加载器。


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


当当前类加载器需要加载一个类的时候,先访问父类加载器有没有,一直访问到启动类加载器,如果没有加载,当前类加载器才去加载这个类。而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







  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值