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

2024-02-11 06:04

本文主要是介绍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 加载的

 

 

完 

 

 

 

这篇关于93 log4j-slf4j-impl 搭配上 log4j-to-slf4j 导致的 StackOverflow的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/699000

相关文章

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

使用雪花算法产生id导致前端精度缺失问题解决方案

《使用雪花算法产生id导致前端精度缺失问题解决方案》雪花算法由Twitter提出,设计目的是生成唯一的、递增的ID,下面:本文主要介绍使用雪花算法产生id导致前端精度缺失问题的解决方案,文中通过代... 目录一、问题根源二、解决方案1. 全局配置Jackson序列化规则2. 实体类必须使用Long封装类3.

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

使用@Slf4j注解,log.info()无法使用问题

《使用@Slf4j注解,log.info()无法使用问题》在使用Lombok的@Slf4j注解打印日志时遇到问题,通过降低Lombok版本(从1.18.x降至1.16.10)解决了问题... 目录@Slf4androidj注解,log.info()无法使用问题最后解决总结@Slf4j注解,log.info(

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

DAY16:什么是慢查询,导致的原因,优化方法 | undo log、redo log、binlog的用处 | MySQL有哪些锁

目录 什么是慢查询,导致的原因,优化方法 undo log、redo log、binlog的用处  MySQL有哪些锁   什么是慢查询,导致的原因,优化方法 数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。 导致的原因: 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询,可能导致执行时间较长。查询数据量大:当查询的数据量庞大时,即使查询本身并不复杂,也可能导致