SLF4j:Log facade abstract

内 容:

 

      现如今,日志框架层出不穷,JDKLogger、Log4j、Logback等这些是最常用的了。然而现在越来越多的框架中,都会在使用日志框架的同时,还会使用到一个门面(slf4j-api.jar),使用这个门面的的最方便的地方大抵是它提供格式化字符串的功能。

 

slf4j 与其他日志框架的关系

在应用程序中,直接使用slf4j-api.jar就可以完成日志的记录,而不用在代码里直接使用某一种日志框架了(虽然最终记录日志还是有日志框架来完成的)。

      

       下面是使用了slf4j时,应用程序、slf4j、slf4j-adapter.jar、日志框架之间的调用关系:

 

 

 

下面是一个简单的示例:

package com.fjn.frame.slf4j;

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

public class HelloWorld {

   public static void main(String[] args) {
      Logger logger = LoggerFactory.getLogger(HelloWorld.class);
      logger.info("Hello World");
   }
}

 

LoggerFactory.getLogger(xxx)要分为两个过程:

1、实例化ILoggerFactory, 这个步骤只是在第一次进行。

2、根据ILoggerFactory实例创建Logger实例。

 

下面就分别来说说这两个过程:

 

ILoggerFactory 实例化的过程

       在使用slf4j时,并不需要指定具体使用哪种日志框架,只需要给定相关的适配器包就可以了。那么如何拿到真正的ILoggerFactory实现,并实例化的呢?

  

1、在LogFactory的classloader的搜索路径下查找 ” org/slf4j/impl/StaticLoggerBinder.class” 类

 

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);
      }
      // use Set instead of list in order to deal with  bug #138
      // LinkedHashSet appropriate here because it preserves insertion order during iteration
      Set implementationSet = new LinkedHashSet();
      while (paths.hasMoreElements()) {
        URL path = (URL) paths.nextElement();
        implementationSet.add(path);
      }
      if (implementationSet.size() > 1) {
        Util.report("Class path contains multiple SLF4J bindings.");
        Iterator iterator = implementationSet.iterator();
        while(iterator.hasNext()) {
          URL path = (URL) iterator.next();
          Util.report("Found binding in [" + path + "]");
        }
        Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
      }
    } catch (IOException ioe) {
      Util.report("Error getting resources from path", ioe);
    }
  }

 如果有多个就会打印:

 

2、将slf4j与指定的实现进行绑定,这一步的操作,通常是:

1)单实例化StaticLoggerBinder。

2)检查StaticLoggerBinder的版本,其实就是需要有一个静态的非final的变量(这个变量不是必须得有的),

StaticLoggerBinder.REQUESTED_API_VERSION

 

3、获取到ILoggerFactory的实例

     一般来说,是返回一个static ILoggerFactory impl=new   XXXLoggerFactory();

由ILoggerFactory来创建Logger实例

 

LoggerFactory创建Logger实例,其实由日志框架本身的LogManager创建一个Logger,然后包装成LoggerAdapter。例如Log4j的适配包中的Log4jLoggerFactory#getLogger(String name)的实现如下:

public Logger getLogger(String name) {
    Logger slf4jLogger = loggerMap.get(name);
    if (slf4jLogger != null) {
      return slf4jLogger;
    } else {
      org.apache.log4j.Logger log4jLogger;
      if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
        log4jLogger = LogManager.getRootLogger();
      else
        log4jLogger = LogManager.getLogger(name);

      Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
      Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
      return oldInstance == null ? newInstance : oldInstance;
    }
  }

  

下面用是一张简易的关系图,显示了适配器的实现:

 

  

自定义日志框架适配器

       在一些公司,肯定还有自己的Logger框架,如果也希望通过slf4j来做日志,就需要写相关的适配器包了。通过上述的两个过程的了解,很容易就能知道如何自定义适配器了。

 自定义适配器中,必须包括3个组件:

· StaticLoggerBinder

        这个类需要遵守下列规约:

        1)  类名必须是org.slf4j.impl.StaticLoggerBinder

        2)  这个类必须是单例的,必须有getSingleton()方法

        3)  尽可能的有 public static String REQUESTED_API_VERSION 字段,并且不能是final的。

        4)  要实现org.slf4j.spi.LoggerFactoryBinder接口。

· LoggerFactoryImpl

       这个类要实现org.slf4j.ILoggerFactory接口

· LoggerAdapter

       这个类要实现org.slf4j.Logger接口。

 

如何选取多个Log框架

SLF4j原则上只会绑定一个适配器,所以通常我们的项目中,最好只使用一种日志框架。然而事与愿违,现在项目中通常都会引入大量第三方框架等,这些框架使用的日志框架又不尽相同,所以引入多个日志框架的情况也是不可避免的。SLF4j初始化时,如果找到了多个相关的适配器,只会使用找到的第一个适配器。

在项目中要尽量减少引入的log框架的数量,通常会使用maven exclusion:

<dependencies>
  <dependency>
    <groupId> org.apache.cassandra</groupId>
    <artifactId>cassandra-all</artifactId>
    <version>0.8.1</version>
 
    <exclusions>
      <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
      </exclusion>
      <exclusion>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
      </exclusion>
    </exclusions>
 
  </dependency>
</dependencies>

 

如何将项目从Commons-Logging迁移到SLF4j


如果项目中(或者引入的第三方jar中)已经使用了commons-logging,要迁移至SLF4j。

需要做两个事:

1、将项目中已使用commons-logging的模块、以及第三方jar。使用maven exclustions排除掉。

2、引入jcl-over-slf4j.jar。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.21</version>
</dependency>

原因:

项目(或者第三方jar)中使用的是commons-logging的API:org.apache.commons.logging.Log、org.apache.commons.logging.LogFactory。

这两个类在jcl-over-slf4j.jar中都有。不过修改了其实现。

在修改后的org.apache.commons.logging.LogFactory的实现是将原来的LogFactory查找过程完全替换成了jcl-over-slf4j.jar包中的一个SLF4jLogFactory:

package org.apache.commons.logging;

import java.util.Hashtable;

import org.apache.commons.logging.impl.SLF4JLogFactory;

/**
 * <p>
 * Factory for creating {@link Log} instances, which always delegates to an
 * instance of {@link SLF4JLogFactory}.
 * 
 * </p>
 * 
 * @author Craig R. McClanahan
 * @author Costin Manolache
 * @author Richard A. Sitze
 * @author Ceki G&uuml;lc&uuml;
 */

public abstract class LogFactory {

  static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";

  static LogFactory logFactory = new SLF4JLogFactory();

   public static LogFactory getFactory() throws LogConfigurationException {
    return logFactory;
  }

   ...
   ...
  
}

而SLF4jLogFactory的实现是委托给slf4j的LoggerFactory去定位日志框架并取得Logger对象,最后将取得的Logger对象再包装成commons-logging中的Log实现:

 

public class SLF4JLogFactory extends LogFactory {
   ... 

     public Log getInstance(String name) throws LogConfigurationException {
    Log instance = loggerMap.get(name);
    if (instance != null) {
      return instance;
    } else {
      Log newInstance;
      Logger slf4jLogger = LoggerFactory.getLogger(name); // 又委托给了Slf4j的 LoggerFactory
      if (slf4jLogger instanceof LocationAwareLogger) {
        newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger);
      } else {
        newInstance = new SLF4JLog(slf4jLogger);
      }
      Log oldInstance = loggerMap.putIfAbsent(name, newInstance);
      return oldInstance == null ? newInstance : oldInstance;
    }
  }

}

 

所以迁移时,一定要完全排除commons-logging的jar,并引入 jcl-over-slf4j.jar

posted @ 2016-01-03 12:19  乐享程序员  阅读(1557)  评论(2编辑  收藏  举报