Java 日志框架冲突问题排查与总结( 二 )

  • Commons Logging:简称 JCL , 是 Apache 的项目 。JCL 是一个 Log Facade , 只提供 Log API , 不提供实现 , 用 Adapter 来使用 Log4j 或者 JUL 作为 Log Implementation 。目的是统一日志接口规范 , 适配多种日志实现 。
  • SLF4J/Logback:SLF4J(The Simple Logging Facade for Java) 和 Logback 也是 Gülcü 创立的项目 , 其创立主要是为了提供更高性能的实现 。其中 , SLF4j 是类似于JCL 的Log Facade , Logback 是类似于Log4j 的 Log Implementation 。这老哥觉得 JCL 的接口设计不好 , 所以重新设计了一套 。
  • Log4j2:维护 Log4j 的人为了不让 Log4j 的用户被 SLF4J/Logback 抢走 , 所以搞出了新的日志框架 。Log4j2 和 Log4j1.x 并不兼容 , 设计上很大程度上模仿了 SLF4J/Logback , 性能上也获得了很大的提升 。Log4j2 也做了 Facade/Implementation 分离的设计 , 分成了 log4j-api 和 log4j-core 。
  • 至此我们已经有了三个的 Log 接口和四个 Log 实现 , 果然程序员真的是爱造轮子 。出现这么多框架之后 , 有人开始搞各个框架之间的桥接 , 你兼容我 , 我兼容你 , 如下图所示 。
    Java 日志框架冲突问题排查与总结

    文章插图
     
    因为很多 jar 使用的日志框架不同 , 所以经常会出现引入 jar 包之后导致日志类冲突 , 前面我们排查的那个问题就是因为引入了 jcl-over-slf4j 的桥接包 。
    动态加载日志实现前面我们提到日志框架分为日志接口和日志实现 , 只要我们代码中使用的是日志接口(JCL、SLF4J) , 我们可以随时替换日志的实现 。
    SLF4J 加载日志实现的方式SLF4J 加载日志实现分为两个步骤:
    • 获取 ILoggerFactory 日志工厂
    • 根据 ILoggerFactory 获取 Logger
    SLF4J 要求日志实现 jar 包都要实现 StaticLoggerBinder 这个类 , 而且要放在指定目录:org/slf4j/impl/StaticLoggerBinder.class , SLF4J 的LoggerFactory会去扫描所有 jar 包中的这个地址 , 参考下面的代码 。
     
    Java 日志框架冲突问题排查与总结

    文章插图
     
    虽然它扫描了多个日志实现 , 但实际上同名类 JVM 只能存在一个 , 它这里扫描的目的是为了打印日志告诉用户有多少个日志实现在依赖包中 。下面的代码返回的是最终使用的日志实现 。
    Java 日志框架冲突问题排查与总结

    文章插图
     
    你可能要问了 , 同时存在多个日志实现类的时候 , 到底是用的是哪个?答案很简单 , 因为 SLF4J 利用了静态类来加载日志工程 , 实际上就是让 JVM 决定使用哪个类:哪个被先加载到 JVM 中就用哪个 。为了搞清楚这个问题的答案 , 我特地去看了URLClassPath加载类的实现 , 它就是按照 jar 加入到 URLClassPath的顺序遍历扫描 , 找到第一个符合条件的就返回 。
    JCL 加载日志实现的方式相比 SLF4J 比较任性的加载方式(依赖 JVM 加载类的顺序) , JCL 提供了更多的配置能力 , 可以指定使用哪一个日志工程类 。
    类似的 , JCL 也分为两个步骤加载日志实现:
    • 获取 LogFactory 日志工厂类
    • 根据 LogFactory 获取 Logger
    首先是获取 LogFactory:
    • 先从系统属性中读取系统属性System.getProperty("org.apache.commons.logging.LogFactory")
    • 使用 Java 的 SPI 机制 , 来搜寻对应的实现:META-INF/services/org.apache.commons.logging.LogFactory , 这里就不对 SPI 进行过多介绍了 , 简单来说就是搜寻哪些 jar 包中含有搜寻含有上述文件 , 该文件中指明了对应的 LogFactory 实现
    • 从 commons-logging 的配置文件中 commons-logging.properties 寻找org.apache.commons.logging.LogFactory的值
    • 最后还没找到的话 , 使用默认的org.apache.commons.logging.impl.LogFactoryImpl
    找到 LogFactory 之后就根据 LogFactory 获取 Logger , 这个根据不同的 LogFactory 实现有不同的方式 。前面我遇到那个问题就是因为类冲突导致使用了 SLJ4J 的 LogFactory  , 加载了错误的 Logger 。
    总结开发过程中总会遇到奇奇怪怪的问题 , 有无处下手的感觉时先稳住心态 , 按照大胆假设 , 小心求证的方式进行排查 , 实在没有思路往往是因为基础还不扎实 。


    推荐阅读