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

作者:Sharehub
来源:blog.xiaohansong.com/JAVA-log-confict-solve.html
前言Java 有很多的日志框架可以选择 , 当同一个项目中出现多种日志框架时就很容易出现日志框架冲突的问题 , 导致日志打印不出来 。本文将以一次典型的日志冲突排查问题为例 , 提供排查步骤和思路 , 最后分析日志框架冲突的原因 。
一般行文思路都是先讲 Why , 再讲 How , 这里我颠倒了 , 因为一般遇到问题的时候我们对问题背后的根本原因是一无所知的 , 如果我们已经知道问题的原因 , 那么问题也就迎刃而解了 。
因此我希望先复现我当时在对日志框架了解不多的情况下排查问题的思路和步骤 , 如何在面对未知问题找到破题思路是非常重要的技能 。
一次典型的日志冲突排查问题背景在 A 工程中 , 日志框架配置选用了 Log4j2 , master 分支上日志打印正常 , 但开发分支增加了代码之后日志打印不出来 。项目的依赖中包含了 Log4j2、Logback 等日志框架 。
排查思路与过程排查问题的时候首先必须要有明确的思路 , 即大胆假设 , 小心求证 , 不能像无头苍蝇一样乱试 。从问题的现象看 , 直觉上可以得出几个假设:
  • 服务器环境有问题
  • 开发分支的 Log4j2 配置有问题
接下来就是验证假设 , 首先多申请几台机器部署项目分支 , 发现问题仍然存在 , 可以排除第一个假设 。其次找到另一个工程 B 跟 A 工程对比 Log4j2 的配置 , 也没有发现明细的差异 , 可以排除第二个假设 。
在已有假设都验证失败的情况下 , 需要收集更多的信息作出判断 , 接下来就是要用对照实验收集信息 。于是我分别断点了 A 和 B 两个工程 , 观察它们日志实体的类型是否一致 。结果发现两者的日志实体类型不一样 , A 的日志实现是 Logback , B 的日志实现是 Log4j2 , 很明显 A 打印不出日志是因为日志实体不对 , 但是两者都是用的同一个 LoggerFactory 创建 Logger 的 。
从对照实验的结果来看 , 可以得出一个假设:依赖冲突导致了 A 运行时使用日志实体不是 Log4j2 。
至此我们已经找到了问题的大致方向 , 接下来就是要排包 。排包一般有两种思路:
  • 暴力求解:把所有可能冲突的日志包排掉 , 一个个试 。
  • 精准爆破:利用类加载的信息判断运行时加载的具体是哪个 jar
暴力求解的方式太花费时间了 , 所以我用的第二种方式 。
获取日志实体的方式如下:
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(xxx.class);LoggerFactory 的代码如下:
 
Java 日志框架冲突问题排查与总结

文章插图
 
从代码上可以发现 , getLog方法是来自父类LogFactory , 当我去尝试获取LogFactory的实现时候 , 发现竟然有 3 个 jar 中都有同样包名的LogFactory实现 。于是我断点了 A 和 B 工程的代码 , 用 IDEA 的运行代码功能执行以下命令获取LogFactory的加载信息 。
Java 日志框架冲突问题排查与总结

文章插图
 
结果发现 B 工程使用是spring-jcl , A 使用的是jcl-over-slf4j , 然后排除掉 A 中jcl-over-slf4j , 问题解决 。
上面的排查过程中 , 关键的地方有两点:
  • 定位到问题的根源是类加载冲突 , 确定排查方向 。
  • 通过断点获取冲突类的加载信息 , 快速定位到冲突的 jar 。
为什么日志框架会冲突问题至此就解决了 , 但是还有一个更深入的问题没有解决:为什么同时存在多个日志框架的时候就会出现冲突呢?在解决完问题之后 , 我深入研究了日志框架的历史和设计 , 发现原来这跟日志框架的实现机制有关系 。
日志框架的历史首先要从日志框架的发展历史开始说起 。