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 加载的

 

 

完 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/464495.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中使用multiprocessing模块创建进程

在计算机编程领域&#xff0c;多进程编程是一种常见的并发编程技术&#xff0c;特别适用于利用多核处理器来提高程序性能和并行处理任务。Python作为一种功能强大的编程语言&#xff0c;提供了多种方法来实现多进程编程。其中&#xff0c;multiprocessing模块为我们提供了一种简…

【Spring MVC篇】参数的传递及json数据传参

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Spring MVC】 本专栏旨在分享学习Spring MVC的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、普通参数的传…

Nodejs基于vue的网上药店药品销售管理系统bxm50

该系统开发是基于B/S模式的&#xff0c;使用nodejs编程语言和js技术&#xff0c;框架使用vue框架&#xff0c;数据库采用MySQL数据库进行存储数据&#xff0c;使用HTML,CSS,等技术进行页面的布局和设计。系统编码使用vscode做开发工具&#xff0c; 系统设计包括几个主要的功能…

VitePress-12-markdown中使用vue的语法

前言 VitePress 中&#xff0c;markdown文档最终都会转换成为 html文件&#xff0c;我们在访问的时候&#xff0c;也是直接访问的 xxx.html 文件。而且&#xff0c;markdown文档会被作为 [vue单文件] 进行处理&#xff0c;因此&#xff0c;我们我们可以在文档中使用 vue 语法&…

MySQL索引怎么提高查询的速度?

目录 一、MySQL介绍 二、什么是索引 三、为什么要用索引 四、索引如何提高查询速度 一、MySQL介绍 MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是目前最流行和广泛使用的数据库之一。MySQL由瑞典MySQL AB公司开发&#xff0c;并在…

Python爬虫实战 | 京东平台电商API接口采集京东商品京东工业商品详情数据

item_get-获得JD商品详情API测试 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_shop等]cac…

甘肃旅游服务平台:技术驱动的创新实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

《统计学简易速速上手小册》第1章:统计学基础(2024 最新版)

文章目录 1.1 数据类型和数据收集1.1.1 基础知识1.1.2 主要案例&#xff1a;顾客满意度调查1.1.3 拓展案例 1&#xff1a;产品销售分析1.1.4 拓展案例 2&#xff1a;员工绩效评估 1.2 描述性统计学1.2.1 基础知识1.2.2 主要案例&#xff1a;销售数据分析1.2.3 拓展案例 1&#…

Maven进阶

一、分模块开发与设计 1. 分模块开发的意义 问题导入 分模块开发对工程有什么好处&#xff1f; 模块拆分原则 目的&#xff1a;项目的扩展性变强了&#xff0c;方便其他项目引用相同的功能。 将原始模块按照功能拆分成若干个子模块&#xff0c;方便模块间的相互调用&#…

Qualcomm 蓝牙耳机 FAQ(41)---------Audio 问题分析之 ACAT Tools安装

大家好&#xff01; 新的一年&#xff0c;在此祝大家&#xff1a;新年快乐&#xff01;工作上步步高升&#xff01;&#xff01;龙年大吉&#xff01;&#xff01;&#xff01; 也欢迎大家登录大大通平台&#xff0c;春节期间正常更新文章&#xff0c;期待你的到来&#xff0…

猫头虎分享已解决Bug || Go Error: Missing Return at End of Function

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Transformer实战-系列教程13:DETR 算法解读

&#x1f6a9;&#x1f6a9;&#x1f6a9;Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 点我下载源码 1、物体检测 说到目标检测你能想到什么 faster-rcnn系列&#xff0c;开山之作&…