程序员开发实例大全宝库

网站首页 > 编程文章 正文

log4j-over-slf4j与slf4j-log4j12导致StackOverflowError分析

zazugpt 2024-08-30 04:58:47 编程文章 17 ℃ 0 评论

log4j-over-slf4j和slf4j-log4j12是跟Java日志系统相关的两个jar包,当它们同时出现在classpath下时,就可能会引起堆栈溢出异常。异常信息大致如下:

Exception in thread "main" java.lang.StackOverflowError

at java.util.Hashtable.containsKey(Hashtable.java:306)

at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:36)

at org.apache.log4j.LogManager.getLogger(LogManager.java:39)

at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)

at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)

at org.apache.log4j.Category.<init>(Category.java:53)

at org.apache.log4j.Logger..<init>(Logger.java:35)

at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)

at org.apache.log4j.LogManager.getLogger(LogManager.java:39)

at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)

at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)

at org.apache.log4j.Category..<init>(Category.java:53)

at org.apache.log4j.Logger..<init>(Logger.java:35)

at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)

at org.apache.log4j.LogManager.getLogger(LogManager.java:39)

subsequent lines omitted...

英文解释:

The purpose of slf4j-log4j12 module is to delegate or redirect calls made to an SLF4J logger to log4j. The purpose of the log4j-over-slf4j module is to redirect calls made to a log4j logger to SLF4J. If SLF4J is bound with slf4j-log4j12.jar and log4j-over-slf4j.jar is also present on the class path, a StackOverflowError will inevitably occur immediately after the first invocation of an SLF4J or a log4j logger.


中文翻译:

slf4J -log4j12模块的目的是将对SLF4J日志记录器的调用委托或重定向到log4j。log4j-over-slf4j模块的目的是将对log4j日志记录器的调用重定向到SLF4J。如果SLF4J与slf4j-log4j12.jar绑定,并且log4j-over-slf4j.jar也出现在类路径上,那么在第一次调用SLF4J或log4j日志记录器之后,StackOverflowError将不可避免地立即发生。

访问Spring Cloud Alibaba技术专栏,了解更多的技术细节和项目代码。


一、现有日志体系

分析这个异常出现的具体原因之前,有必要先快速了解一下现有的Java日志体系。下图是现有Java日志体系的一个示意:

上图比较清晰地展示现有Java日志体系的主体架构。Java日志体系大体可以分为三个部分:日志门面接口、桥接器、日志框架具体实现。


Java日志框架有很多种,最简单的是Java自带的java.util.logging,而最经典的是log4j,后来又出现了一个比log4j性能更好的logback,其他的日志框架就不怎么常用了。应用程序直接使用这些具体日志框架的API来满足日志输出需求当然是可以的,但是由于各个日志框架之间的API通常是不兼容的,这样做就使得应用程序丧失了更换日志框架的灵活性。


比直接使用具体日志框架API更合理的选择是使用日志门面接口。日志门面接口提供了一套独立于具体日志框架实现的API,应用程序通过使用这些独立的API就能够实现与具体日志框架的解耦,这跟JDBC是类似的。最早的日志门面接口是commons-logging,但目前最受欢迎的是slf4j。


日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,就好像JDBC与各种不同的数据库之间的结合需要对应的JDBC驱动一样。


但是,这只是主要部分,实际情况并不总是简单的“日志门面接口-->桥接器-->日志框架”这一条单向线。实际上,独立的桥接器有时候是不需要的,而且也并不是只有将日志门面API转调到具体日志框架API的桥接器,也存在将日志框架API转调到日志门面API的桥接器。


如果同时存在A-to-B.jar和B-to-A.jar这两个桥接器,从而导致A和B一直不停地互相递归调用,最后堆栈溢出。这就是最开始引出的那个stack overflow异常的基本原理。


二、序列图分析

上图是堆栈溢出的详细调用过程序列图。从调用1开始,依次调用1.1、1.1.1……最后到了1.1.1.1.1.1(图中最后一个调用)的时候,发现它跟1是完全一样的,那么后续的过程就是完全一样的重复了。


三、Spring Boot应用的解决方案

在Spring Boot应用中,在使用log4j时,出现java.lang.StackOverflowError异常时,具体异常信息如下:

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/C:/Users/Rickie/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/C:/Users/Rickie/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

Exception in thread "main" java.lang.StackOverflowError

at org.apache.logging.slf4j.SLF4JLoggerContextFactory.getContext(SLF4JLoggerContextFactory.java:51)

at org.apache.logging.log4j.LogManager.getContext(LogManager.java:307)

at org.apache.log4j.LogManager$PrivateManager.getContext(LogManager.java:232)

at org.apache.log4j.LogManager.getLogger(LogManager.java:89)

at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:83)

at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)

at org.apache.logging.slf4j.SLF4JLoggerContext.getLogger(SLF4JLoggerContext.java:39)

at org.apache.log4j.Category.<init>(Category.java:78)

at org.apache.log4j.Logger.<init>(Logger.java:33)

at org.apache.log4j.Category$PrivateAdapter.newLogger(Category.java:506)

at org.apache.log4j.Category.getInstance(Category.java:118)

at org.apache.log4j.Category.getInstance(Category.java:98)

at org.apache.log4j.LogManager.getLogger(LogManager.java:89)

at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:83)

at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)

at org.apache.logging.slf4j.SLF4JLoggerContext.getLogger(SLF4JLoggerContext.java:39)


可以在pom文件中排除重复的jar包引用即可,使用exclusions元素,排除掉spring-boot-starter-logging包。

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

<exclusions>

<exclusion>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-logging</artifactId>

</exclusion>

</exclusions>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-log4j2</artifactId>

</dependency>

访问Kafka技术专栏,了解更多的技术细节和项目代码。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表