Java 常用日志框架介绍(下)

阅读数:117 2019 年 9 月 24 日 18:19

Java常用日志框架介绍(下)

8.3 Slf4j 调用过程源码分析,加入 slf4j-api-version.jar,与 Logback 组件

Slf4j 作为门面采用 Logback 作为实现或者采用其它上面提到过的组件作为实现类似,这里只分析采用 Logback 组件作为实现

8.3.1 示例代码

https://github.com/chlsmile/slf4j-logback-demo

8.3.2 pom 核心配置如下

复制代码
1<dependencies>
2 <dependency>
3 <groupId>org.slf4j</groupId>
4 <artifactId>slf4j-api</artifactId>
5 <version>1.7.13</version>
6 </dependency>
7 <!--logback-classic 依赖 logback-core,会自动级联引入 -->
8 <dependency>
9 <groupId>ch.qos.logback</groupId>
10 <artifactId>logback-classic</artifactId>
11 <version>1.2.3</version>
12 </dependency>
13 </dependencies>

8.3.3 程序入口类同上

8.3.4 源码追踪分析

1)、2)、3)、4)同上

5)调用 LoggerFactory 的

findPossibleStaticLoggerBinderPathSet() 方法获取 StaticLoggerBinderPath 集合

Java常用日志框架介绍(下)

6)调用 LoggerFactory 的 bind() 方法的 staticLoggerBinderPathSet 集合对象赋值

Java常用日志框架介绍(下)

7)在 LoggerFactory 的 bind() 方法中调用 loback 包下的 StaticLoggerBinder 创建单例对象

Java常用日志框架介绍(下)

8)在 LoggerFactory 的 bind() 方法中调用 reportActualBinding() 记录日志加载信息

Java常用日志框架介绍(下)

Java常用日志框架介绍(下)

9)LoggerFactory 中 INITIALIZATION_STATE 的值为 SUCCESSFUL_INITIALIZATION,调用 StaticLoggerBinder 的单例对象获取 ILoggerFactory

Java常用日志框架介绍(下)
Java常用日志框架介绍(下)

10)此时 LoggerFactory 中的 getLogger() 方法中获取到的 ILoggerFactory 实际上是 logback jar 下的 LoggerContext

Java常用日志框架介绍(下)

11)此时 LoggerFactory 调用 getLogger() 方法获取到的 Logger 实际上是 logback jar 下的 Logger

Java常用日志框架介绍(下)
Java常用日志框架介绍(下)

8.4 Slf4j 调用过程源码分析,加入 slf4j-api-version.jar,同时加入多种日志实现组件

在项目中如果用 slf4j-api 作为日志门面,有多个日志实现组件同时存在,例如同时存在 Logback,slf4j-log4j12,slf4j-jdk14,slf4j-jcl 四种实现,则在项目实际运行中,Slf4j 的绑定选择绑定方式将有 Jvm 确定,并且是随机的,这样会和预期不符,实际使用过程中需要避免这种情况。

8.4.1 示例代码

https://github.com/chlsmile/slf4j-logback-log4j-demo

8.4.2 pom 核心配置如下

复制代码
1 <dependencies>
2 <dependency>
3 <groupId>junit</groupId>
4 <artifactId>junit</artifactId>
5 <version>4.11</version>
6 </dependency>
7 <dependency>
8 <groupId>org.slf4j</groupId>
9 <artifactId>slf4j-log4j12</artifactId>
10 <version>1.7.25</version>
11 </dependency>
12 <dependency>
13 <groupId>ch.qos.logback</groupId>
14 <artifactId>logback-classic</artifactId>
15 <version>1.2.3</version>
16 </dependency>
17 <dependency>
18 <groupId>org.slf4j</groupId>
19 <artifactId>slf4j-jdk14</artifactId>
20 <version>1.7.25</version>
21 </dependency>
22 <dependency>
23 <groupId>org.slf4j</groupId>
24 <artifactId>slf4j-jcl</artifactId>
25 <version>1.7.25</version>
26 </dependency>
27 </dependencies>

8.4.3 程序入口类同上

8.4.4 源码追踪分析

基本步骤同上,这里只追踪主要不同点

1)追踪 LoggerFactory 的 bind() 方法内部调用 findPossibleStaticLoggerBinderPathSet() 方法后,从 classpath 下 4 个 jar 包内找到 StaticLoggerBinder

Java常用日志框架介绍(下)

2)此时 LoggerFactory 的 bind() 方法内部调用 reportMultipleBindingAmbiguity() 方法,给出警告信息 classpath 下同时存在多个 StaticLoggerBinder,JVM 会随机选择一个 StaticLoggerBinder

Java常用日志框架介绍(下)

9 桥接遗留的 api

在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如 Spring Framework 使用的是日志组件是 Commons Logging,XSocket 依赖的则是 Java Util Logging。当我们在同一项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要统一日志方案,统一使用 Slf4j,把他们的日志输出重定向到 Slf4j,然后 Slf4j 又会根据绑定器把日志交给具体的日志实现工具。Slf4j 带有几个桥接模块,可以重定向 Log4j,JCL 和 java.util.logging 中的 Api 到 Slf4j。

9.1 遗留的 api 桥接方案

Java常用日志框架介绍(下)

9.2 桥接方式参见下图

Java常用日志框架介绍(下)

9.3 使用 Slf4j 桥接要注意事项

在使用 Slf4j 桥接时要注意避免形成死循环,在项目依赖的 jar 包中不要存在以下情况。

Java常用日志框架介绍(下)

9.4 遗留 api 桥接死循环源码分析源码

这里以项目中集成 log4j-over-slf4j 与 slf4j-log4j12 为例,其它组合形成死循环原理相类似。

9.4.1 示例代码

https://github.com/chlsmile/slf4j-Infinite-loop-demo

9.4.2 程序入口类同上

9.4.3 源码追踪分析

基本步骤同上,调用链路 LoggerFactory.getLogger()>LoggerFactory.getILoggerFactory()> LoggerFactory.performInitialization()>LoggerFactory.bind()

1)LoggerFactory.bind() 方法内部调用 StaticLoggerBinder.getSingleton() 获取 StaticLoggerBinder 实例

Java常用日志框架介绍(下)

2)StaticLoggerBinder 调用构造方法内部调用 Log4jLoggerFactory 构造方法创建 ILoggerFactory

Java常用日志框架介绍(下)

3)Log4jLoggerFactory 加载内部 static 代码块,校验出 classpath 下存在 org.apache.log4j.Log4jLoggerFactory,抛出异常

Java常用日志框架介绍(下)

10 排除第三方日志依赖

在实际使用过程中,项目会根据需要引入一些第三方组件,例如常用的 Spring,而 Spring 本身的日志实现使用了 Commons Logging,我们又想使用 Slf4j+Loback 组合,这时候需要在项目中将 Commons Logging 排除掉,通常会用到以下 3 种方案,3 种方案各有利弊,可以根据项目的实际情况选择最适合自己项目的解决方案。

10.1 方案一 采用 maven 的 exclusion 方案

复制代码
1<dependency>
2 <groupId>org.springframework</groupId>
3 <artifactId>spring-core</artifactId>
4 <exclusions>
5 <exclusion>
6 <groupId>commons-logging</groupId>
7 <artifactId>commons-logging</artifactId>
8 </exclusion>
9 </exclusions>
10 <version>${springframework.version}</version>
11</dependency>

这种方案优点是 exclusion 是 maven 原生提供的,不足之处是如果有多个组件都依赖了 commons-logging,则需要在很多处增加,使用起来不太方便。

10.2 方案二 在 maven 声明 commons-logging 的 scope 为 provided

复制代码
1<dependency>
2 <groupId>commons-logging</groupId>
3 <artifactId>commons-logging</artifactId>
4 <version>1.1.1</version>
5 <scope>provided</scope>
6</dependency>
7<dependency>
8 <groupId>org.slf4j</groupId>
9 <artifactId>jcl-over-slf4j</artifactId>
10 <version>1.8.0-beta2</version>
11</dependency>

这种方案在调试代码时还是有可能导致 IDE 将 commons-logging 放置在 classpath 下,从而导致程序运行时出现异常。

10.3 方案三 在 maven 私服中增加类似于 99.0-does-not-exist 这种虚拟的版本号

复制代码
1<dependency>
2 <groupId>commons-logging</groupId>
3 <artifactId>commons-logging</artifactId>
4 <version>99.0-does-not-exist</version>
5</dependency>
6<dependency>
7 <groupId>org.slf4j</groupId>
8 <artifactId>jcl-over-slf4j</artifactId>
9 <version>1.8.0-beta2</version>
10</dependency>

这种方案好处是声明方式比较简单,用 IDE 调试代码时也不会出现问题,不足之处是 99.0-does-not-exist 这种版本是 maven 中央仓库中是不存在的,需要发布到自己的 maven 私服中。

11 总结

由于历史原因 JDK 自身提供的 Log 组件出现的较晚,导致 Jdk 提供 Log 组件时第三方社区的日志组件已经比较稳定成熟。经过多年的发展 Slf4j+Logback 与组合,Commons Logging 与 Log4j 组合两大阵营已经基本成为了 Java 项目开发的标准,建议在新的项目开发中从这两种方案中选择适合自己项目的组合方案。

作者介绍:
圣卡西(企业代号名),目前负责贝壳找房 java 后台开发工作。

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/FWnh71eN5jxiu7aBOiUHaA

评论

发布