1. 絮絮叨叨
- 学习或工作中,如果需要从头建立日志打印体系,笔者通常直接照抄之前的博客:《Java maven工程配置slf4j》,直接粘贴、复制相关依赖
- 除了上述博客提到的slf4j-api、logback-classic,也看到过slf4j-simple、log4j、log4j2等,但实际不清楚它们之间的关系
- 最近的工作,促使笔者简单恶补了日志框架
- 关于日志框架的发展,从Log4j → \rightarrow → JUL(Java Util Logging) → \rightarrow → Apache Cmmons Logging → \rightarrow → SLF4J:Java日志框架介绍和 Slf4j 使用
- 关于SLF4J(Simple logging Facade for Java)
- SLF4J是一个日志门面,而非具体的日志框架。它提供了统一的日志记录接口,可以对接不同的日志系统
- 只要按照其提供方式记录日志,最终日志的格式、级别、输出方式等都由绑定的具体日志系统来实现和决定
- 也就说,想要使用打印日志,需要绑定具体的日志框架,例如:SLF4J + Logback的常见组合
- 参考文档:Java 学习笔记 - 日志体系:SLF4J 是啥?与各日志框架啥关系?、Java日志框架:slf4j作用及其实现原理
2. 关于报错 SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder” 的可能原因
- 在使用SLF4J时,可能会遇到如下报错
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
- 最常见的原因:新手不会使用SLF4J,只引入了SLF4J,没有引入具体的日志框架
- 但笔者遇到的原因则比较少见:SLF4J + Logback组合,二者的version不匹配导致
2.1 未引入具体的日志框架
-
自建一个maven项目,引入SLF4J依赖
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency>
-
在代码中打印日志
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggerTest { private static final Logger logger = LoggerFactory.getLogger(LoggerTest.class); public static void main(String[] args) { logger.info("Hello world!"); } }
-
由于未引入具体的日志框架,将导致程序运行报错
-
从报错信息给出的链接,将得到该报错的可能原因
- 该报错信息来自
slf4j-api 1.7.x
或更早版本,slf4j-api 2.x
或更高版本不再使用StaticLoggerBinder,而是使用 ServiceLoader 机制 - 错误原因:classpath中没有发现合适的SLF4J binding,也就是没有具体日志框架,引入
slf4j-nop.jar
slf4j-simple.jar
,slf4j-log4j12.jar
,slf4j-jdk14.jar
或logback-classic.jar
中的任一一种就可以解决问题
- 该报错信息来自
-
笔者选择使用logback作为日志实现,添加如下依赖便可以解决问题
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency>
-
最终成功打印日志
2.2 SLF4J + Logback组合,二者的version不匹配
- 笔者最近工作的项目,使用的是
slf4j-api 2.0.7
+logback 1.4.5
版本的日志组合 - 但由于需要上报指标,引入第三方依赖metrics,从而引入了
slf4j-api 1.7.21
项目的commons模块 --> 第三方metrics依赖 --> httpasyncclient-shade-1.0.10.jar --> slf4j-api-1.7.21.jar
- 最终,服务启动时加载到了隐形的
slf4j-api-1.7.21.jar
。与该版本适配的是logback 1.2.x
版本,而非服务中已有的logback 1.4.5
- 这是一个多节点的分布式服务,笔者查看日志时发现:有的节点无任何日志输出,服务启动日志报错:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
- 因此,笔者怀疑:metrics依赖导致服务日志体系被破坏
2.2.1 曲折的排查过程
- 由于笔者一开始没有仔细阅读官方描述,更没有想过存在隐形的低版本slf4j-api,各种调整依赖引入,折腾了1天多皆无果
- 最终,静下心来、重整旗鼓后,从stackoverflow得到了启发:SLF4J with logback still prompt failed to load class “org.slf4j.impl.StaticLoggerBinder”,怀疑是版本不匹配
- 但笔者多次检查过服务的classpath,确定提供的均是
slf4j-api 2.0.7
+logback 1.4.5
的jar包,没有其他版本的slf4j-api或logback - 最终,通过arthas sc命令查看服务中加载slf4j依赖,发现无法打印日志的节点,加载的slf4j来自
httpasyncclient-shade-1.0.10.jar
,而非预期的、classpath中的slf4j-api-2.0.7.jar
- 使用的arthas命令如下:
# 查看加载的slf4j相关类 sc org.slf4j.* # 查看Logger类的详细加载信息 sc -d org.slf4j.Logger
- 对于成功打印日志的节点,可以看出加载的slf4j是符合预期
- 对于无法打印日志的节点,加载的slf4j则来自第三方依赖
- 使用的arthas命令如下:
- 通过验证
slf4j 1.7.21 + logback 1.4.5
组合,以及咨询依赖提供方,发现slf4j确实是1.7.x版本,且与logback 1.4.5
版本组合会触发报错:SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
- 与依赖提供方确认后,将
httpasyncclient-shade-1.0.10.jar
排除掉,再重新打包、部署服务,发现日志打印回复正常
2.2.2 SLF4J + Logback的版本适配问题
- 从之前SLF4J官网对错误(
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
)的解释推测:- slf4j-api 1.7.x及更低版本,使用StaticLoggerBinder,对应的logback 1.2.x或更低版本?
- slf4j-api 2.0.x及更高版本,使用 ServiceLoader 机制,对应的logback 1.3.x或更高版本
- 博客Java Logging Part 2: Logging and Package Exclusion with SLF4J + Logback的介绍更加详细
- 同时,从Logback的官网看:logback 1.3.x和1.4.x要求slf4j-api 2.0.x版本