zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    一行代码更改博客园皮肤
    fatal: refusing to merge unrelated histories
    使用 netcat 传输大文件
    linux 命令后台运行
    .net core 使用 Nlog 配置文件
    .net core 使用 Nlog 集成 exceptionless 配置文件
    Mysql不同字符串格式的连表查询
    Mongodb between 时间范围
    VS Code 使用 Debugger for Chrome 调试vue
    css权重说明
  • 原文地址:https://www.cnblogs.com/f1194361820/p/5096014.html
Copyright © 2011-2022 走看看