• 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

  • 相关阅读:
    JavaScript得到当前窗口的所有大小值
    JavaScript 变量、作用域和内存问题
    jQuery html5Validate基于HTML5表单验证插件
    新世界
    2001年的火花
    High Dynamic Range Compression on Programmable Graphics Hardware
    运筹帷幄
    你还要在学校找什么东西?
    图行天下
    Supra Team
  • 原文地址:https://www.cnblogs.com/f1194361820/p/5096014.html
Copyright © 2020-2023  润新知