当前位置: 首页 > news >正文

93 log4j-slf4j-impl 搭配上 log4j-to-slf4j 导致的 StackOverflow

前言

呵呵 最近想要 做一个 mongo 低版本的客户端读取高版本的服务端传递过来的数据造成的一个错误的时候, 出现了这样的问题  

引入了 mongo-java-driver 之后, 使用相关 api 的时候会触发 com.mongo.internal.connection.BaseCluser 的初始化, 其依赖的 Loggers 间接的依赖的是 org.slf4j.LoggerFactory 来获取 logger 

然后 启动一个简单的 public static void main, 之后就 StackOverflow 了 

稍微看了一下, 是由于 log4j-slf4j-impl 和 log4j-to-slf4j 依赖, 导致的一个比现的一个问题 

因此 来一个 case 看一下 具体的情况 

也顺便 看了一下 slf4j 和 log4j, 以及他们的一些关联关系, 之前 也是稀里糊涂的复制过来直接使用, 没有怎么关注这个 

简单的来说 slf4j 是一套接口, 一套约束, 适配了各种实现, slf4j-api.jar 属于接口的约束, log4j-slf4j-impl 属于 log4j 适配 slf4j 系列接口的具体的实现 

log4j 是一种具体的日志输出的实现, 和 logback, slf4j-simple, jdk的Logger 都属于具体的 impl 

 

 

测试用例

依赖如下

        <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.10.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-to-slf4j</artifactId><version>2.13.2</version></dependency>

 

测试用例

package com.hx.test;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** Test01LoggerStackOverflow** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2021-10-23 12:08*/
public class Test01LoggerStackOverflow {// loggerpublic static final Logger LOGGER = LoggerFactory.getLogger(Test01LoggerStackOverflow.class);// Test01LoggerStackOverflowpublic static void main(String[] args) {LOGGER.error("Hello World");}}

 

启动之后抛出异常如下, 是在 LoggerFactory.getLogger 的时候 

Exception in thread "main" java.lang.StackOverflowErrorat org.apache.logging.log4j.util.StackLocatorUtil.getCallerClass(StackLocatorUtil.java:55)at org.apache.logging.slf4j.Log4jLoggerFactory.getContext(Log4jLoggerFactory.java:42)at org.apache.logging.log4j.spi.AbstractLoggerAdapter.getLogger(AbstractLoggerAdapter.java:46)at org.apache.logging.slf4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:29)at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:355)at org.apache.logging.slf4j.SLF4JLoggerContext.getLogger(SLF4JLoggerContext.java:39)at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:37)at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:29)at org.apache.logging.log4j.spi.AbstractLoggerAdapter.getLogger(AbstractLoggerAdapter.java:52)at org.apache.logging.slf4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:29)at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:355)at org.apache.logging.slf4j.SLF4JLoggerContext.getLogger(SLF4JLoggerContext.java:39)at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:37)at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:29)at org.apache.logging.log4j.spi.AbstractLoggerAdapter.getLogger(AbstractLoggerAdapter.java:52)at org.apache.logging.slf4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:29)

 

 

问题的调试

这个问题 我们要关注两个地方, 一个是 LoggerFactory 的选择, 一个是 LoggerContextFactory 的选择 

slf4j-simple 里面的实现就相对简单, 直接通过 LoggerFactory 创建 Logger 

但是 log4j-slf4j-impl 里面需要实际工作的 Logger 是 log4j 的 Logger, log4j 中构建 Logger 需要构建 LoggerContext, 并基于 LoggerContext 来创建 Logger 

 

 

LoggerFactory 的选择

首先来看一下 LoggerFactory 的一些初始化的地方  

这里获取 Slf4jServiceProvier 是通过 serviceloader 来实现的, 我们这里只能够获取到一个 provider, 这个 是 log4j-slf4j-impl 里面的一个具体的实现 

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

比如我现在增加一个 slf4j-simple 的一个实现, 那么 这里可以获取到两个 Slf4jServiceProvier 

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

存在多个 Slf4jServiceProvider 的情况下, 就会输出我们常见的如下日志信息, 并且日志中输出了 选择的是哪一个 Slf4jServiceProvider, 选择的规则是选择第一个  

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

 

LoggerContextFactory 的选择 

我们来具体看一下 log4j-slf4j-impl 里面 LoggerFactory 创建 Logger 的具体的实现 

看这里创建了一个 context, 并使用 context 创建 Logger, 按道理来说, 我们期望这个 context.getLogger 应该是一个具体的创建 Logger 的地方 

但是可以看到这里的 context 的到的是一个 Slf4jLoggerContext[是在 log4j-to-slf4j.jar 的包中], 这个 context 里面没有创建 Logger, 而是吧创建 Logger 的业务委托给了 org.slf4j.LoggerFactory, 所以 这里就构成了一个没有退出边界的递归, 造成了 StackOverflow 

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

我们来看一下 正常的的情况下, 可以看到 这里获取到的 LoggerContext 是 log4j-core 下面的 

然后实际的工作, 也是创建真实的 log4j 的 Logger  

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16 

 

为什么 log4j-slf4j-impl 和 log4j-to-slf4j 都存在的时候, 会选择到 Slf4jLoggerContext 呢 ?

AbstractLoggerAdapter 中创建 LoggerContext 是委托给了 LogManager 

LogManager 中创建 LoggerContext 取决于具体的 factory, 也就是 LoggerContextFactory 

ProviderUtil 中使用 servicelaoder 加载对应的 Provider 加载了两个, 一个是 Log4jContextFactory, 一个是 Slf4jLoggerContextFactory, 并且 Slf4jLoggerContextFactory 的优先级比 Log4jContextFactory 要高, 因此优先选择的是 Slf4jLoggerContextFactory 

这两个 Provider 分别是来自于 log4j-slf4j-impl 和 log4j-to-slf4j 这两个包中 

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

 

所以 如果你不需要 log4j-to-slf4j 的话, 可以直接排除掉这个包避免问题 

当然 还有其他一些方式, 搞一个优先级更高的 Provider 

 

假设我们调整一下 Log4jContextFactory 的优先级

可以按到这里将  Log4jContextFactory 的优先级调整到了 20, 因此选择的 LoggerContextFactory 是 Log4jContextFactory, 做了真实的事情 

然后 最终程序运行正常, 符合我们期望的结果 

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16 

 

log4j-to-slf4j.jar 是干什么?

看一下 Slf4jLogger, 实现的 log4j 的接口, 入参是 slf4j 的 Logger, 那就是一个 slf4j 适配 log4j 的一个工具类

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

Slf4jLoggerContext 的内容, 主要是从上下文获取 slf4j 的 Logger, 然后将它转换成 Slf4jLogger[实现的是 log4j 的接口]

watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd6aOOOQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 

 

slf4j1.7.x 版本和 slf4j1.8.x 版本的上述流程的差异

主要的差异在于 1.7.x 版本中 Slf4jServiceProvider 的角色为 StaticLoggerBinder  

并且 1.7.x 中加载 StaticLoggerBinder 是基于 classloader 加载的, 不是基于 serviceloader 加载的

 

 

完 

 

 

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Rust语言入门小结(第1篇)
  • SQL,HQL刷题,尚硅谷
  • 【MySQL】字符串函数的学习
  • 使用代理IP有风险吗?如何安全使用代理IP?
  • STM32 硬件随机数发生器(RNG)
  • GNU C和标准C
  • Redis(十三)缓存双写一致性策略
  • 在Ubuntu22.04上部署ComfyUI
  • 【51单片机】外部中断和定时器中断
  • 【数据结构】链表OJ面试题5(题库+解析)
  • Java异常处理 throw和throws
  • 黄金交易策略(Nerve Knife):反趋势锁定单的处理机制
  • RISC-V指令格式
  • 2024.2.5 vscode连不上虚拟机,始终waiting for server log
  • 极值图论基础
  • co模块的前端实现
  • Flannel解读
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Java深入 - 深入理解Java集合
  • JS变量作用域
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • select2 取值 遍历 设置默认值
  • unity如何实现一个固定宽度的orthagraphic相机
  • 高度不固定时垂直居中
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 利用DataURL技术在网页上显示图片
  • 悄悄地说一个bug
  • 微信小程序开发问题汇总
  • 以太坊客户端Geth命令参数详解
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 白色的风信子
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (26)4.7 字符函数和字符串函数
  • (7)STL算法之交换赋值
  • (CPU/GPU)粒子继承贴图颜色发射
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (办公)springboot配置aop处理请求.
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (二)JAVA使用POI操作excel
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (精确度,召回率,真阳性,假阳性)ACC、敏感性、特异性等 ROC指标
  • (五)IO流之ByteArrayInput/OutputStream
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)JAVA中的堆栈
  • (转)视频码率,帧率和分辨率的联系与区别
  • (自用)gtest单元测试
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .“空心村”成因分析及解决对策122344
  • .form文件_一篇文章学会文件上传
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料