写点什么

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

  • 2019-09-24
  • 本文字数:3315 字

    阅读完需:约 11 分钟

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 集合



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



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



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




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




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



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



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



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


9 桥接遗留的 api

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

9.1 遗留的 api 桥接方案

9.2 桥接方式参见下图

9.3 使用 Slf4j 桥接要注意事项

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


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 实例



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



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


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


2019-09-24 18:191265

评论

发布
暂无评论
发现更多内容

网络入侵检测系统之Suricata(十三)--网络安全威胁及攻击手段总览

于顾而言

网络安全 suricata

架构师的十八般武艺:架构方法论

agnostic

TOGAF Zachman

Flutter - Google 开源的移动 UI 框架

陈橘又青

9月月更

4 张图了解 CI/CD 基础~

掘金安东尼

前端 9月月更

Redis的事件

急需上岸的小谢

9月月更

网络入侵检测系统之Suricata(十一)--TCP重组实现详解

于顾而言

网络安全 suricata

网络入侵检测系统之Suricata(十六)--类suricata/snort规则自动维护工具

于顾而言

网络安全 suricata

MyBatisPlus(四、代码生成器)

Mybatis-Plus 代码生成 9月月更

redis对应的数据类型及其底层原理

知识浅谈

redis 底层原理 9月月更

架构之美第一篇-概述

Marvin

架构 企业架构 架构模式 架构三要素 架构发展历史

[极致用户体验] 微信设置大字号后,iOS加载网页时闪动怎么办?

HullQin

CSS JavaScript html 前端 9月月更

「趣学前端」Taro实践+踩坑记录第一期

叶一一

taro 前端 框架 9月月更

[SpringMVC]拦截器②(拦截器参数、拦截器链配置)

十八岁讨厌编程

springmvc 后端开发 9月月更

[SpringMVC]拦截器①(概述、入门案例)

十八岁讨厌编程

springmvc 后端开发 9月月更

一文带你快速入门【哈希表】

Fire_Shield

数据结构 哈希表 9月月更

SAP UI5 Form 表单 Column Layout 下的 Column 个数分配问题

汪子熙

JavaScript Fiori SAP UI5 ui5 9月月更

「趣学前端」自己动手丰衣足食的TS项目开发

叶一一

typescript 前端 ts 9月月更

网络入侵检测系统之Suricata(十四)--匹配流程

于顾而言

网络安全 suricata

架构师的十八般武艺:架构目标

agnostic

架构目标

jQuery之实战

楠羽

笔记 JQuery框架 9月月更

网络入侵检测系统之Suricata(十二)--TCP重组优化

于顾而言

网络安全 suricata

大数据调度平台Airflow(一):什么是Airflow

Lansonli

9月月更

C++学习---cstdio的源码学习分析03-文件重命名函数rename

桑榆

c++ 源码阅读 9月月更

[Maven进阶]分模块开发与设计

十八岁讨厌编程

maven 后端开发 9月月更

Qt|实现简单的分割窗口

中国好公民st

qt 分割 9月月更

gopher成长之路(五): 2年前和2年后同一个项目

非晓为骁

个人成长

力扣93 - 复原IP地址【回溯算法】

Fire_Shield

LeetCode 字符串 9月月更

网络入侵检测系统之Suricata(十五)--IPOnly/Radix Tree详解

于顾而言

网络安全 suricata

【算法实践】分块查找知多少?手把手带你实现分块查找

迷彩

数据结构 算法 9月月更 分块查找 查找算法

王者荣耀商城异地多活架构设计

张立奎

Java进阶(二十七)使用Dom4j解析XML文件

No Silver Bullet

Java xml 9月月更 DOM4J

Java常用日志框架介绍(下)_文化 & 方法_圣卡西_InfoQ精选文章