百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java 日志框架冲突解决方案,太全面了

ccwgpt 2024-10-01 08:12 29 浏览 0 评论

你是否遇到过配置了日志,但打印不出来的情况?你是否遇到过配置了 logback,启动时却提示 log4j 错误的情况?像下面这样:
log4j:WARN No appenders could be found for logger (org.example.App).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

你是否遇到过 SLF4J 的这种报错?

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.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 [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

你是否遇到过 DUBBO 日志打印不正常的情况?

你是否遇到过 Mybatis SQL 日志打印不出来的情况?

你是否遇到过 JPA/Hibernate SQL 日志无法打印的情况?

你是否遇到过复杂项目中,很多框架内部日志无法打印的情况?

你是否遇到过 Tomcat 工程,日志文件打印了多份,catalina.out 和其他文件?

你是否遇到过 SpringBoot 项目,日志文件打印了多份的问题?

你是否遇到过各种日志配置问题……

日志框架的冲突

上面的这些问题,基本都是由于多套日志框架共存或配置错误导致的。那么为什么会出现共存或者冲突呢?

一般是以下几种原因:

  1. 项目手动引用了各种日志框架的包 - 比如同时引用了 log4j/log4j2/logback/jboss-logging/jcl 等

  2. 包管理工具的传递依赖(Transitive Dependencies)导致,比如依赖了 dubbo,但是 dubbo 依赖了 zkclient,可 zkclient 又依赖了 log4j,此时如果你的项目中还有其他日志框架存在并有使用,那么就会导致多套共存

  3. 同一个日志框架多版本共存

JAVA 里的各种日志框架

在正式介绍冲突和解决之前,需要先简单的说一下 Java 中的各种日志框架:

Java 中的日志框架分为两种,分别为日志抽象/门面,日志实现

日志抽象/门面

日志抽象/门面,他们不负责具体的日志打印,如输出到文件、配置日志内容格式等。他们只是一套日志抽象,定义了一套统一的日志打印标准,如 Logger 对象,Level 对象。

slf4j(Simple Logging Facade for Java)和jcl(Apache Commons Logging)这两个日志框架就是 JAVA 中最主流的日志抽象了。还有一个jboss-logging,主要用于 jboss 系列软件,比如 hibernate 之类。像 jcl已经多年不更新了(上一次更新时间还是 14 年),目前最推荐的是使用 slf4j

日志实现

Java 中的日志实现框架,主流的有以下几种:

  1. log4j - Apache(老牌日志框架,不过多年不更新了,新版本为 log4j2)

  2. log4j2 - Apache(log4j 的新版本,目前异步 IO 性能最强,配置也较简单)

  3. logback - QOS(slf4j 就是这家公司的产品)

  4. jul(java.util.logging) - jdk 内置

在程序中,可以直接使用日志框架,也可以使用日志抽象+日志实现搭配的方案。不过一般都是用日志抽象+日志实现,这样更灵活,适配起来更简单。

目前最主流的方案是 slf4j+logback/log4j2,不过如果是 jboss 系列的产品,可能用的更多的还是 jboss-logging ,毕竟亲儿子嘛。像 JPA/Hibernate 这种框架里,内置的就是 jboss-logging

SpringBoot + Dubbo 日志框架冲突的例子

举个例子来说个最常见的传递依赖导致的共存冲突

比如我有一个“干净的”spring-boot 项目,干净到只有一个spring-boot-starter依赖,此时我想集成 dubbo,使用 zookeeper 作为注册中心,此时我的依赖配置是这样:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.9</version>
</dependency>
</dependencies>

现在启动这个 spring-boot 项目,会发现一堆红色错误:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.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 [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
----------------------------------人肉分割线----------------------------------------
log4j:WARN No appenders could be found for logger (org.apache.dubbo.common.logger.LoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

从错误提示上看,错误内容分为两个部分:

  1. slf4j 报错,提示找到多个 slf4j 的日志绑定

  2. log4j 报错,提示 log4j 没有 appender 配置

出现这个错误,就是因为 dubbo 的传递依赖中含有 log4j,但是 spring-boot 的默认配置是 slf4j+logback。在依赖了 dubbo 相关包之后,现在项目中同时存在logback/jcl(apache commons-logging)/log4j/jul-to-slf4j/slf4j-log4j/log4j-to-slf4j

来看一下依赖图:


这个时候就乱套了,slf4j-log4j是 log4j 的 slf4j 实现,作用是调用 slf4j api 的时候使用 log4j 输出;而log4j-to-slf4j的作用是将log4j的实现替换为log4j,这样一来不是死循环了

而且还有 logback 的存在,logback 默认实现了 slf4j 的抽象,而slf4j-log4j也是一样实现了 slf4j 的抽象,logback,项目里共存了两套slf4j的实现,那么在使用slf4j接口打印的时候会使用哪个实现呢?

答案是“第一个”,也就是第一个被加载的 Slf4j 的实现类,但这种依靠 ClassLoader 加载顺序来保证的日志配置顺序是非常不靠谱的

如果想正常使用日志,让这个项目里所有的框架都正常打印日志,必须将日志框架统一。不过这里的统一并不是至强行修改,而是用“适配/中转”的方式。

现在项目里虽然有 slf4j-log4j 的配置,但这个配置是适配 log4j2 用的,而我们的依赖了只有 log4j1,实际上这个中转是无效的。但 logback 是有效的,而且是 spring-boot 项目的默认配置,这次就选择 logback 作为项目的统一日志框架吧。

现在项目里存在 log4j(1)的包,而且启动时又报 log4j 的错误,说明某些代码调用了 log4j 的 api。但我们又不想用 log4j,所以需要先解决 log4j 的问题。

由于有 log4j 代码的引用,所以直接删除 log4j 一定是不可行的。slf4j 提供了一个log4j-over-slf4j的包,这个包复制了一份 log4j1 的接口类(Logger 等),同时将实现类修改为 slf4j 了。

所以将 log4j 的(传递)依赖排除,同时引用log4j-over-slf4j,就解决了这个 log4j 的问题。现在来修改下 pom 中的依赖(查看依赖图可以使用 maven 的命令,或者是 IDEA 自带的 Maven Dependencies Diagram,再或者 Maven Helper 之类的插件)

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.9</version>
<scope>compile</scope>
<!--排除log4j-->
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!--增加log4j-slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>

解决了 log4j 的问题之后,现在还有 slf4j 有两个实现的问题,这个问题处理就更简单了。由于我们计划使用 logback,那么只需要排除/删除slf4j-log4j这个实现的依赖即可

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.9</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

修改完成,再次启动就没有错误了,轻松解决问题

日志适配大全

上面只是介绍了一种转换的方式,但这么多日志框架,他们之间是可以互相转换的。不过最终目的都是统一一套日志框架,让最终的日志实现只有一套 ** 这么多的日志适配/转换方式,全记住肯定是有点难。为此我画了一张可能是全网最全的日志框架适配图(原图尺寸较大,请点击放大查看),如果再遇到冲突,需要将一个日志框架转换到另一款的时候,只需要按照图上的路径,引入相关的依赖包即可。



比如想把 slf4j,适配/转换到 log4j2。按照图上的路径,只需要引用 log4j-slf4j-impl 即可。

如果想把 jcl,适配/转换到 slf4j,只需要删除 jcl 包,然后引用 jcl-over-slf4j 即可。

图上的箭头,有些标了文字的,是需要额外包进行转换的,有些没有标文字的,是内置了适配的实现。其实内置实现的这种会更麻烦,因为如果遇到共存基本都需要通过配置环境变量/配置额外属性的方式来指定一款日志实现。

目前 slf4j 是适配方案中,最核心的那个框架,算是这个图的中心枢纽。只要围绕 slf4j 做适配/转化,就没有处理不了的冲突

总结

解决日志框架共存/冲突问题其实很简单,只要遵循几个原则:

  1. 统一使用一套日志实现

  2. 删除多余的无用日志依赖

  3. 如果有引用必须共存的话,那么就移除原始包,使用“over”类型的包(over 类型的包复制了一份原始接口,重新实现)

  4. 不能 over 的,使用日志抽象提供的指定方式,例如jboss-logging中,可以通过org.jboss.logging.provider环境变量指定一个具体的日志框架实现

项目里统一了日志框架之后,无论用那种日志框架打印,最终还是走向我们中转/适配后的唯一一个日志框架。

解决了共存/冲突之后,项目里就只剩一款日志框架。再也不会出现“日志打不出”,“日志配置不生效”之类的各种恶心问题,下班都能早点了!

原文:https://juejin.cn/post/6945220055399399455


如果看到这里,说明你喜欢这篇文章,请

相关推荐

Java七大热门技术框架源码解析(25章) 完结

获课》aixuetang.xyz/5699/Hibernate与MyBatis源码级PK:ORM框架的两种哲学在Java持久层框架领域,Hibernate与MyBatis代表了两种截然不同的设计哲学。...

【25章】Java七大热门技术框架源码解析

获课》aixuetang.xyz/5699/Java高级面试:七大框架源码精讲与实战解析在当今Java技术生态中,对主流框架源码的深入理解已成为高级开发者面试的核心竞争力。掌握Spring、MyBat...

饿了么董事长吴泽明兼任CEO,韩鎏分管即时物流中心

饿了么调整组织架构。2月11日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明...

饿了么100%迁至阿里云,快速扩容可支持1亿人同时点单

来源:环球网6月17日,记者获悉,饿了么已完成100%上云,所有业务系统、数据库设施等均已迁移至阿里云。高峰期,饿了么可在阿里云上快速扩容,可以支持1亿人同时在线点单,这意味着饿了么的服务能力再次全面...

饿了么组织架构调整:董事长吴泽明兼任CEO 韩鎏专注即时物流中心管理

近日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明在内部信中表示,考虑即时物...

饿了么组织架构调整:董事长吴泽明兼任CEO

Tech星球2月11日消息,据新浪科技报道,今日饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴...

饿了么又调整了组织架构,董事长吴泽明兼任CEO

2月11日,饿了么董事长,花名为范禹的吴泽明,通过公司全员信宣布最新组织调整:从即日起,吴泽明将兼任饿了么CEO。公司原CEO,花名为昊宸的韩鎏今后专注分管即时物流中心,继续向吴泽明汇报。在内部信中,...

SpringBoot项目快速开发框架JeecgBoot——Web处理!

Web处理JeecgBoot框架主要用于Web开发领域。下面介绍JeecgBoot在Web开发中的常用功能,如控制器、登录、系统菜单、权限模块的角色管理和用户管理。首先启动后台项目,将其导入IDE...

腾讯即将开源Kuikly:基于Kotlin的纯原生跨端解决方案

IT之家3月4日消息,腾讯日前在端服务网站发布预告,即将开源Kuikly跨端开发框架。预告海报介绍称,Kuikly是基于KotlinKMM技术、客户端开发友好的全新跨端解决方案,可...

Python构建MCP服务器完整教程:5步打造专属AI工具调用系统

模型控制协议(ModelControlProtocol,MCP)是一种专为实现AI代理与工具解耦而设计的通信协议,为AI驱动应用程序的开发提供了高度的灵活性和模块化架构。通过MCP服务器,AI代...

Python3使用diagrams生成架构图(python模块制作)

目录技术背景diagrams的安装基础逻辑关系图组件簇的定义总结概要参考链接技术背景对于一个架构师或者任何一个软件工程师而言,绘制架构图都是一个比较值得学习的技能。这就像我们学习的时候整理的一些Xmi...

Python 失宠!Hugging Face 用 Rust 新写了一个 ML框架,现已低调开源

大数据文摘受权转载自AI前线整理|褚杏娟近期,HuggingFace低调开源了一个重磅ML框架:Candle。Candle一改机器学习惯用Python的做法,而是Rust编写,重...

Python Web 框架(Python Web 框架)

Tornado、Flask、Django三个PythonWeb框架的主要区别和适用场景:特点/框架TornadoFlaskDjango类型异步非阻塞Web服务器和框架轻量级微框架全功能...

构建并发布你的自定义 Python 包(python如何创建自定义模块)

Python让你可以重用代码,并将代码分享给他人以节省时间和精力。所以,当你编写了一些方便的脚本,希望你的同事或其他人也能使用时,接下来该怎么做呢?这篇文章就来解决打包和分发的问题。我们将专注于将你...

Python 应用开发框架 BeeWare 简明实用教程

1.BeeWare简介BeeWare是一个Python框架,用于开发跨平台原生应用。它支持Android、iOS、Windows、macOS和Linux,并提供原生用户体验。2.安装B...

取消回复欢迎 发表评论: