Log 日志

作者: 93张先生 | 来源:发表于2020-09-14 10:09 被阅读0次

    三方日志的初始化

    运行时决定第三方日志

    LogFactory 类加载时会运行静态代码块,以此尝试 Slf4j、JakartaCommonsLogging、Log4j2、Log4j、Jdk14Log、NoLog 等多种第三方日志,直到 logConstructor 不为 Null,此时就确定了使用哪一种第三方日志。

      static {
        // 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
      }
    
    /**
       * 如果 logConstructor == null,就不会再执行 run(),
       * 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
       * @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
       *
       */
      private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
          try {
            // run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
            runnable.run();
          } catch (Throwable t) {
            // ignore
          }
        }
      }
    
      private static void setImplementation(Class<? extends Log> implClass) {
        try {
          //  三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
          // 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
          Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
          // LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
          // 构造器参数
          Log log = candidate.newInstance(LogFactory.class.getName());
          if (log.isDebugEnabled()) {
            log.debug("Logging initialized using '" + implClass + "' adapter.");
          }
          logConstructor = candidate;
        } catch (Throwable t) {
          throw new LogException("Error setting Log implementation.  Cause: " + t, t);
        }
      }
    

    自定义配置 Log 类

    1. mybatis-config.xml 配置第三方 Log
    2. 解析 setting 配置
    3. 解析配置的 logImpl 三方日志
      Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
    // 1. mybatis-config.xml 配置第三方 Log
    <setting name="logImpl" value="LOG4J"/>
    // 2. 解析 setting 配置
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    // 3. 解析配置的 logImpl 三方日志
    loadCustomLogImpl(settings);
    
    /**
       * 加载 <setting> 配置的 log 日志实现
       * @param props
       */
      private void loadCustomLogImpl(Properties props) {
        // logImpl 全路径名称
        Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
        configuration.setLogImpl(logImpl);
      }
    
    // 4. Configuration 调用 LogFactory.useCustomLogging(this.logImpl); 覆盖 初始化时的第三方 Log 日志
    //  设置 <setting> 中 第三方 log  的日志实现
      public void setLogImpl(Class<? extends Log> logImpl) {
        if (logImpl != null) {
          this.logImpl = logImpl;
          LogFactory.useCustomLogging(this.logImpl);
        }
      }
    
      // 加载 <setting> 配置的第三方日志
      public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
        setImplementation(clazz);
      }
    

    Log 对象的获取

    在 LogFactory 工厂类,创建具体的 Log 对象。
    根据初始化的 logConstructor 对象实例,获取具体的 Log 对象,用来记录日志

      /**
       * 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
       *
       * 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
       * 这个参数用来记录,哪个类,使用了 log 对象
       * 返回 log 对象
       * @param aClass 使用 log 对象的类名称
       * @return
       */
      public static Log getLog(Class<?> aClass) {
        // 类名称
        return getLog(aClass.getName());
      }
    
      /**
       * 根据一个类名称,返回一个 Log 对象
       * logConstructor 是项目初始化的时候,进行了创建赋值
       * @param logger 构造器的参数,要记录日志的类的名称
       * @return
       */
      public static Log getLog(String logger) {
        try {
          // logger 是 构造器参数
          return logConstructor.newInstance(logger);
        } catch (Throwable t) {
          throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
        }
      }
    

    日志适配器

    第三方日志组件都有各自的 Log 级别,且都有所不同, Log4j2 则提供了 trace debug info warn error fatal 这六种日志级别。 MyBatis 提供了 trace debug warn error 四个级别。

    三方日志 UML 图

    image.png

    LogFactory

    LogFactory 工厂类负责创建对应的日志组件适配器 。
    此类 第一步:创建 logConstructor 对象实例,然后根据 logConstructor 对象实例获取具体的 Log 对象,用来记录日志。

    public final class LogFactory {
    
      /**
       * Marker to be used by logging implementations that support markers.
       */
      public static final String MARKER = "MYBATIS";
    
      private static Constructor<? extends Log> logConstructor; // 记录当前使用的第三方日志组件所对应的适配器的构造方法
      // 以此从上之下,尝试初始化第三方日志模块,直到 logConstructor != null
      static {
        // 函数式接口,执行顺序 tryImplementation,然后执行 函数表达式,再执行函数表达式中的 setImplementation()
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
      }
    
      private LogFactory() {
        // disable construction
      }
    
      /**
       * 样例:private static final Log log = LogFactory.getLog(BaseExecutor.class);
       *
       * 获取的 Log 对象实例的构造方法的参数包含类名称,所以有了 aClass 这个参数
       * 这个参数用来记录,哪个类,使用了 log 对象
       * 返回 log 对象
       * @param aClass 使用 log 对象的类名称
       * @return
       */
      public static Log getLog(Class<?> aClass) {
        // 类名称
        return getLog(aClass.getName());
      }
    
      /**
       * 根据一个类名称,返回一个 Log 对象
       * logConstructor 是项目初始化的时候,进行了创建赋值
       * @param logger 构造器的参数,要记录日志的类的名称
       * @return
       */
      public static Log getLog(String logger) {
        try {
          // logger 是 构造器参数
          return logConstructor.newInstance(logger);
        } catch (Throwable t) {
          throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
        }
      }
      // 加载 <setting> 配置的第三方日志
      public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
        setImplementation(clazz);
      }
    
      public static synchronized void useSlf4jLogging() {
        setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
      }
    
      public static synchronized void useCommonsLogging() {
        setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
      }
    
      public static synchronized void useLog4JLogging() {
        setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
      }
    
      public static synchronized void useLog4J2Logging() {
        setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
      }
    
      public static synchronized void useJdkLogging() {
        setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
      }
    
      public static synchronized void useStdOutLogging() {
        setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
      }
    
      public static synchronized void useNoLogging() {
        setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
      }
    
      /**
       * 如果 logConstructor == null,就不会再执行 run(),
       * 在 本类上面 static 代码块,会多次调用这个方法,根据调用顺序优先级,只会初始化一个 logConstructor,logConstructor 只会被一个三方日志中的一个初始化。
       * @param runnable 作为 java8 的新特性,函数式接口;和 Thread 没有半毛钱关系;
       *
       */
      private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
          try {
            // run() 函数式接口中方法的执行,比如这个 LogFactory::useSlf4jLogging lambda 表达式
            runnable.run();
          } catch (Throwable t) {
            // ignore
          }
        }
      }
    
      private static void setImplementation(Class<? extends Log> implClass) {
        try {
          //  三方日志的带有 String 参数的构造器,通过构造器创建 log 实例,并 赋值给 logConstructor
          // 获取 String 类型参数 的构造器;因为 Log4j2Impl 等具体第三方构造器的 参数是 String 类型
          Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
          // LogFactory.class.getName() 仅作为 log 具体实现类中构造函数的参数;
          // 构造器参数
          Log log = candidate.newInstance(LogFactory.class.getName());
          if (log.isDebugEnabled()) {
            log.debug("Logging initialized using '" + implClass + "' adapter.");
          }
          logConstructor = candidate;
        } catch (Throwable t) {
          throw new LogException("Error setting Log implementation.  Cause: " + t, t);
        }
      }
    
    }
    

    Jdk14LoggingImpl 适配器

    Jdk14LoggingImpl 实现了 org.apache.ibatis logging.Log 接口,并封装了 java.util.logging.Logger对象, org.apche.ibatis.logging.Log 接口的功能全部通过调用 java.util.logging.Logger 对象现, 这就是前面介绍的适配器模式。

    public class Jdk14LoggingImpl implements Log {
    
      // 底层封装的 java.util.logging.Logger 对象
      private final Logger log;
    
      // 通过 logConstructor.newInstance(logger) 初始化 java.util.logging.Logger 对象; logger 为调用 log 日志的类名称
      public Jdk14LoggingImpl(String clazz) {
        log = Logger.getLogger(clazz);
      }
    
      @Override
      public boolean isDebugEnabled() {
        return log.isLoggable(Level.FINE);
      }
    
      @Override
      public boolean isTraceEnabled() {
        return log.isLoggable(Level.FINER);
      }
    
      @Override
      public void error(String s, Throwable e) {
        log.log(Level.SEVERE, s, e);
      }
    
      @Override
      public void error(String s) {
        log.log(Level.SEVERE, s);
      }
    
      @Override
      public void debug(String s) {
        log.log(Level.FINE, s);
      }
    
      @Override
      public void trace(String s) {
        log.log(Level.FINER, s);
      }
    
      @Override
      public void warn(String s) {
        log.log(Level.WARNING, s);
      }
    
    }
    
    

    相关文章

      网友评论

        本文标题:Log 日志

        本文链接:https://www.haomeiwen.com/subject/maxoektx.html