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://xiahunao.cn/news/2777855.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

解决MapboxGL的Popup不支持HTMLDiv元素的问题

解决MapboxGL的Popup不支持HTMLDiv元素的问题 官网给出的文档是不支持HTMLDivElement的&#xff0c;只支持HTML标签。 如果单纯的只显示字符串&#xff0c;那就没问题&#xff0c;如果想在Popup中使用更强大的功能&#xff0c;此时就不行了&#xff0c;下面是源码的一部分显示…

python从入门到精通(十):python爬虫的BeautifulSoup4

python爬虫的BeautifulSoup4 BeautifulSoup4导入模块解析文件创建对象python解析器beautifulsoup对象的种类Tag获取整个标签获取标签里的属性和属性值Navigablestring 获取标签里的内容BeautifulSoup获取整个文档Comment输出的内容不包含注释符号BeautifulSoup文档遍历Beautifu…

金融信贷风控系统设计

前言 近一年多以来在金融行业负责风控系统&#xff0c;根据自己工作中的经验&#xff0c;写下这篇文章。既是对自己在风控领域工作的总结&#xff0c;也是给刚入行和准备入行的朋友打个样&#xff0c;希望能有所帮助。 为什么要有风控系统 记得 2016 年信贷行业的发展形势还…

JavaScript综合练习4

JavaScript 综合练习 4 1. 案例演示 2. 代码实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title&…

[职场] 如何通过运营面试_1 #笔记#媒体#经验分享

如何通过运营面试 盈利是公司的事情&#xff0c;而用户就是你运营的事情。你需要彻底建立一个庞大而有效的用户群&#xff0c;这样才能让你们的公司想盈利就盈利&#xff0c;想战略就战略&#xff0c;想融资就融资。 一般从事运营的人有着强大的自信心&#xff0c;后台数据分析…

M1 Mac使用SquareLine-Studio进行LVGL开发

背景 使用Gui-Guider开发遇到一些问题,比如组件不全。使用LVGL官方的设计软件开发 延续上一篇使用的基本环境。 LVGL项目 新建项目 选择Arduino的项目,设定好分辨率及颜色。 设计UI 导出代码 Export -> Create Template Project 导出文件如图 将libraries下的ui文…

微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 目录 前言 通过代码实现限流 定义资源 通过代码定义资源 通过注解方式定义资源 定义限流规则 通过…

第三章:光效果产生立体感

本文是《从0开始图形学》笔记的第三章&#xff0c;上一章中我们已经将箱子的整个形状“找出来”了&#xff0c;但是还仅仅只是一个色块区域。这一节我们就利用光将整个箱子的立体感凸显出来。涉及到布林冯光照模型以及向量的点乘运算。 概念解说 这里需要用到“布林冯”光照模…

C语言函数(三):数组和函数实现扫雷游戏

目录 1.扫雷游戏的分析和设计1.1.扫雷游戏的功能说明1.2.游戏的分析与设计1.2.1 数据结构的分析1.2.2 文件结构设计 2.扫雷游戏的代码实现 1.扫雷游戏的分析和设计 1.1.扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩游戏或者退出游戏扫雷的棋盘…

网课:数独挑战——牛客(题解与疑问)

题目描述 数独是一种填数字游戏&#xff0c;英文名叫 Sudoku&#xff0c;起源于瑞士&#xff0c;上世纪 70 年代由美国一家数学逻辑游戏杂志首先发表&#xff0c;名为 Number Place&#xff0c;后在日本流行&#xff0c;1984 年将 Sudoku 命名为数独&#xff0c;即 “独立的数…

刘谦春晚纸牌魔术背后的数学—海明码原理简介

在昨天2024年的春晚舞台上&#xff0c;魔术大师刘谦以一场令人拍案叫绝的纸牌魔术再度震撼全场。他巧妙地利用了数学原理&#xff0c;精准无误地让观众“随机”选择的纸牌完成了配对&#xff0c;尤其是令人忍俊不禁的是主持人尼格买提的纸牌却没有如愿配对&#xff0c;小尼碎了…

机器学习复习(8)——逻辑回归

目录 逻辑函数&#xff08;Logistic Function&#xff09; 逻辑回归模型的假设函数 从逻辑回归模型转换到最大似然函数过程 最大似然函数方法 梯度下降 逻辑函数&#xff08;Logistic Function&#xff09; 首先&#xff0c;逻辑函数&#xff0c;也称为Sigmoid函数&#…

安全之护网(HVV)、红蓝对抗

文章目录 红蓝对抗什么是护网行动&#xff1f;护网分类护网的时间 什么是红蓝对抗红蓝对抗演练的目的什么是企业红蓝对抗红蓝对抗价值参考 红蓝对抗 什么是护网行动&#xff1f; 护网的定义是以国家组织组织事业单位、国企单位、名企单位等开展攻防两方的网络安全演习。进攻方…

C++ 贪心 区间问题 区间选点

给定 N 个闭区间 [ai,bi] &#xff0c;请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N &#xff0c;表示区间数。 接下来 N 行&#xff0c;每行包含两…

洛谷p4391 无限传输

考察字符串周期的题 题目链接 结论 要求字串 s s s的最短循环字串长就是&#xff1a; a n s n − p m t [ n ] ansn-pmt[n] ansn−pmt[n] 证明如下&#xff1a; 这是最大的前缀和后缀 现在我们做如下操作&#xff1a; 补全字段 a a a和字段 b b b&#xff0c;按子段 a a a的…

Linux操作系统基础(五):Linux的目录结构

文章目录 Linux的目录结构 一、Linux目录与Windows目录区别 二、常见目录介绍&#xff08;记住重点&#xff09; Linux的目录结构 一、Linux目录与Windows目录区别 Linux的目录结构是一个树型结构 Windows 系统 可以拥有多个盘符, 如 C盘、D盘、E盘 Linux 没有盘符 这个概…

AJAX——AJAX入门

1 什么是AJAX&#xff1f; Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于在Web应用程序中实现异步通信的技术。 简单点说&#xff0c;就是使用XMLHttpRequest对象与服务器通信。它可以使用JSON、XML、HTML和test文本等格式发送和接收数据。 AJAX最吸…

机器学习系列——(十一)回归

引言 在机器学习领域&#xff0c;回归是一种常见的监督学习任务&#xff0c;它主要用于预测数值型目标变量。回归分析能够通过对输入特征与目标变量之间的关系建模&#xff0c;从而对未知数据做出预测。 概念 回归是机器学习中的一种监督学习方法&#xff0c;用于预测数值型目…

一个坐标系查询网站python获取所有坐标系

技术路线选择 我是使用的vue 3开发的网页界面&#xff0c;element-plus构建网页组件&#xff0c;openlayer展示地图&#xff0c;express提供后端API&#xff0c;vercel进行在线部署。 python获取所有坐标系 想要展示所有坐标系&#xff0c;那需要先获取坐标系&#xff0c;怎么…

Openwifi 开源项目解读(一)

Openwifi 是一个关于wifi 系统的开源项目&#xff0c;是一个少有的优秀的关于wifi的开源项目&#xff0c;项目中包括了wifi的基带、lowmac、linux驱动 等三部分&#xff0c;其中基带、lowmac部分是在FPGA中实现&#xff0c;wifi驱动部分是运行在Linux下&#xff0c;因此openwif…