Java 日志体系(二)jcl 和 slf4j

  1. 《java 日志体系(一)统一日志》:https://www.cnblogs.com/binarylei/p/9828166.html
  2. 《Java 日志体系(二)jcl 和 slf4j》:https://www.cnblogs.com/binarylei/p/10781582.html

前面介绍了 jdk 自带的 logging、log4j1、log4j2、logback 等实际的日志框架。对于开发者而言,每种日志都有不同的写法。如果我们以实际的日志框架来进行编写,代码就限制死了,之后就很难再更换日志系统,很难做到无缝切换。

所以我们应该是按照一套统一的 API 来进行日志编程,实际的日志框架来实现这套 API,这样的话,即使更换日志框架,也可以做到无缝切换。这就是 commons-logging 与 slf4j 的初衷。

下面就来介绍下 commons-logging 与 slf4j 这两个门面如何与上述四个实际的日志框架进行集成的呢。

一、SLF4J 和 Commons-Logging 如何绑定具体的日志实现

《SLF4J 和 Commons-Logging 日志工具的区别》:http://ifeve.com/simplifying-distinction-between-sl4j/

编译时绑定和运行时绑定

当我第一次阅读关于编译时绑定时,感觉很模糊:一个 java 库如何能用不同的依赖编译时绑定的框架来记录日志?答案是“编译时”绑定只适用于这样的情况-对 SLF4J logger 的实现时,SLF4J “被编译”。然而,你仍可以在运行时使用不同的绑定。

SLF4J 不使用类加载器,而是,很简单:它加载 org.slf4j.impl.StaticLoggerBinder。每一个 SLF4J 的实现(例如 slf4j-log4j 绑定)提供一个有确切名称的类。所以这里没有疑惑,在运行时,相同的情况发生了:类被从类路径里直接取出,没有任何魔术运行。如果在类路劲下没有 slf4j 实现方法会怎么样? 怎样…然后会没有任何日志。

二、commons-logging

《jcl 与 jul、log4j1、log4j2、logback 集成》:https://jybzjf.iteye.com/blog/2238792

《Commons-Logging 存在的 ClassLoader 问题详解》:https://yq.aliyun.com/articles/46888

2.1 简单的使用

引入 maven 依赖,以 log4j 为例:

  1. <dependency>
  2. <groupId>commons-logging</groupId>
  3. <artifactId>commons-logging</artifactId>
  4. <version>1.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>log4j</groupId>
  8. <artifactId>log4j</artifactId>
  9. <version>1.2.17</version>
  10. </dependency>

使用如下,日志将以 log4j 输出:

  1. @Test
  2. public void test() {
  3. Log log = LogFactory.getLog(JclTest.class);
  4. log.info("jcl log");
  5. }

2.2 commons-logging 绑定日志实现

LogFactory.getLog(JclTest.class) 的源码如下:

  1. public static Log getLog(Class clazz) throws LogConfigurationException {
  2. return getFactory().getInstance(clazz);
  3. }

上述获取 Log 的过程大致分成 2 个阶段

  • 获取 LogFactory 的过程 (从字面上理解就是生产 Log 的工厂)。commons-logging 默认提供的 LogFactory 实现:LogFactoryImpl
  • 根据 LogFactory 获取 Log 的过程。commons-logging 默认提供的 Log 实现:Jdk14Logger、Log4JLogger、SimpleLog。

来看下 commons-logging 包中的大概内容:

下面来详细说明:

2.2.1 获取 LogFactory 的过程

从下面几种途径来获取 LogFactory

(1) 系统属性中获取

  1. System.getProperty("org.apache.commons.logging.LogFactory")

(2) 使用 java 的 SPI 机制

对于 java 的 SPI 机制,详细内容可以自行搜索,这里不再说明。搜寻路径如下:

  1. META-INF/services/org.apache.commons.logging.LogFactory

简单来说就是搜寻哪些 jar 包中含有搜寻含有上述文件,该文件中指明了对应的 LogFactory 实现

(3) 从 commons-logging 的配置文件

commons-logging 也是可以拥有自己的配置文件的,名字为 commons-logging.properties,只不过目前大多数情况下,我们都没有去使用它。如果使用了该配置文件,尝试从配置文件中读取属性 "org.apache.commons.logging.LogFactory" 对应的值

(4) 默认的 org.apache.commons.logging.impl.LogFactoryImpl

  1. LogFactoryImpl commons-logging 提供的默认实现

2.2.2 根据 LogFactory 获取 Log 的过程

这时候就需要寻找底层是选用哪种类型的日志

就以 commons-logging 提供的默认实现为例,来详细看下这个过程:

(1) 从 commons-logging 的配置文件中寻找 Log 实现类的类名

  1. commons-logging.properties 配置文件中寻找属性为 "org.apache.commons.logging.Log" 对应的 Log 类名

(2) 从系统属性中寻找 Log 实现类的类名

  1. System.getProperty("org.apache.commons.logging.Log")

(3) 如果上述方式没找到,则从 classesToDiscover 属性中寻找

classesToDiscover 属性值如下:

  1. private static final String[] classesToDiscover = {
  2. "org.apache.commons.logging.impl.Log4JLogger",
  3. "org.apache.commons.logging.impl.Jdk14Logger",
  4. "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
  5. "org.apache.commons.logging.impl.SimpleLog"
  6. };

它会尝试根据上述类名,依次进行创建,如果能创建成功,则使用该 Log,然后返回给用户。

三、slf4j

相比于 commons-logging 采用 classloader 这种复杂的绑定方式,SLF4J 提供了更简单、更明确和同样动态的方法。

SLF4J 不使用类加载器,而是,很简单:它加载 org.slf4j.impl.StaticLoggerBinder。每一个 SLF4J 的实现(例如slf4j-log4j 绑定)提供一个有确切名称的类。

  1. @Test
  2. public void test() {
  3. Logger logger = LoggerFactory.getLogger(Slf4jTest.class);
  4. logger.info("slf4j log");
  5. }

我们来看一下 LoggerFactory.getLogger(Slf4jTest.class) 到底做了什么事情呢?slf4j-api-1.7.25.jar 下 LoggerFactory#getLogger 方法如下:

  1. public static Logger getLogger(String name) {
  2. ILoggerFactory iLoggerFactory = getILoggerFactory();
  3. return iLoggerFactory.getLogger(name);
  4. }

LoggerFactory#getLogger 调用链如下,最终调用 StaticLoggerBinder#getSingleton 方法,这个类由具体的桥接包来实现。

  1. // getLogger -> getILoggerFactory -> performInitialization -> bind -> StaticLoggerBinder.getSingleton()
  2. public static ILoggerFactory getILoggerFactory() {
  3. // 1. 初始化日志系统,调用 performInitialization -> bind -> StaticLoggerBinder.getSingleton()
  4. if (INITIALIZATION_STATE == UNINITIALIZED) {
  5. synchronized (LoggerFactory.class) {
  6. if (INITIALIZATION_STATE == UNINITIALIZED) {
  7. INITIALIZATION_STATE = ONGOING_INITIALIZATION;
  8. performInitialization();
  9. }
  10. }
  11. }
  12. // 2. 日志系统初始化成功,则返回 StaticLoggerBinder.getSingleton().getLoggerFactory()
  13. switch (INITIALIZATION_STATE) {
  14. case SUCCESSFUL_INITIALIZATION:
  15. return StaticLoggerBinder.getSingleton().getLoggerFactory();
  16. // 3. 没有 StaticLoggerBinder 则不输出任何日志
  17. case NOP_FALLBACK_INITIALIZATION:
  18. return NOP_FALLBACK_FACTORY;
  19. case FAILED_INITIALIZATION:
  20. throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
  21. case ONGOING_INITIALIZATION:
  22. // support re-entrant behavior.
  23. // See also http://jira.qos.ch/browse/SLF4J-97
  24. return SUBST_FACTORY;
  25. }
  26. throw new IllegalStateException("Unreachable code");
  27. }

以 logback-classic-1.2.3.jar 为例,其包下也有 org.slf4j.impl.StaticLoggerBinder 类。 StaticLoggerBinder#getLoggerFactory 返回了 LoggerContext 类。LoggerContext 也实现了 ILoggerFactory 接口,即 getLogger(String name) 方法。

  1. public ILoggerFactory getLoggerFactory() {
  2. if (!initialized) {
  3. return defaultLoggerContext;
  4. }
  5. if (contextSelectorBinder.getContextSelector() == null) {
  6. throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
  7. }
  8. return contextSelectorBinder.getContextSelector().getLoggerContext();
  9. }

参考:

  1. 《jcl 与 jul、log4j1、log4j2、logback 集成》:https://jybzjf.iteye.com/blog/2238792
  2. 《Commons-Logging 存在的 ClassLoader 问题详解》:https://yq.aliyun.com/articles/46888
  3. 《SLF4J 和 Commons-Logging 日志工具的区别》:http://ifeve.com/simplifying-distinction-between-sl4j/

每天用心记录一点点。内容也许不重要,但习惯很重要!

Java 日志体系(二)jcl 和 slf4j的更多相关文章

  1. Java日志体系(四)slf4j

    1.1 简介 与commons-logging相同,slf4j也是一个通用的日志接口,在程序中与其他日志框架结合使用,并对外提供服务. Simple Logging Facade for Java简称 ...

  2. java 日志体系目录

    java 日志体系目录 1.1 java 日志体系(一)log4j1.log4j2.logback.jul.jcl.slf4j 1.2 java 日志体系(二)jcl 和 slf4j 2.1 java ...

  3. Java 日志体系

    Java 日志体系 <java 日志和 SLF4J 随想>:http://ifeve.com/java-slf4j-think/ 一.常用的日志组件 名称 jar 描述 log4j log ...

  4. 混乱的 Java 日志体系

    混乱的 Java 日志体系 2016/09/10 | 分类: 基础技术 | 0 条评论 | 标签: LOG 分享到: 原文出处: xirong 一.困扰的疑惑 目前的日志框架有 jdk 自带的 log ...

  5. Java日志体系居然这么复杂?——架构篇

    本文是一个系列,欢迎关注 日志到底是何方神圣?为什么要使用日志框架? 想必大家都有过使用System.out来进行输出调试,开发开发环境下这样做当然很方便,但是线上这样做就有麻烦了: 系统一直运行,输 ...

  6. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  7. java 日志体系(三)log4j从入门到详解

    java 日志体系(三)log4j从入门到详解 一.Log4j 简介 在应用程序中添加日志记录总的来说基于三个目的: 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作: 跟踪代 ...

  8. java日志体系的思考(转)

    Java 日志缓存机制的实现 Java 日志管理最佳实践 混乱的 Java 日志体系 log日志远程统一记录 浅谈后端日志系统 Java异常处理和接口约定 用SLF4j/Logback打印日志-1 用 ...

  9. 【原创】架构师必备,带你弄清混乱的JAVA日志体系!

    引言 还在为弄不清commons-logging-xx.jar.log4j-xx.jar.sl4j-api-xx.jar等日志框架之间复杂的关系而感到烦恼吗? 还在为如何统一系统的日志输出而感到不知所 ...

随机推荐

  1. gogs windows

    首先安装 git,然后下载 gogs. 在gogs 文件夹位置 启动. gogs.exe web 打开浏览器,输入 127.0.0.1:3000 ,安装 gogs,注意数据库选择,仓库根目录,管理员帐 ...

  2. 搭建IntelliJ IDEA授权服务器

    地址:https://blog.csdn.net/maozexijr/article/details/79072287    https://www.jianshu.com/p/754d8f907f2 ...

  3. JS时间戳转时间

    function timestampToTime(timestamp) { S = timestamp, T = new Date(1E3 * S), Format = function(Q){ret ...

  4. 《深入理解java虚拟机》读书笔记——java内存区域和内存溢出异常

    几种内存溢出异常: 堆溢出 原因:创建过多对象,并且GC Roots到对象之间有可达路径. 分两种情况: Memory Leak:无用的对象没有消除引用,导致无用对象堆积.例如<Effictiv ...

  5. jeecg之弹窗插件lhgdialog小结

    说到弹窗,在jeecg中弹窗用到最多的地方无非是新增/编辑的弹窗. 1.列表页面新增编辑按钮触发的弹窗即lhgdialog,不论是add/update,最终走的都是curdtools.js中的crea ...

  6. jakarta-taglibs-standard-1.1.0查找下载

  7. Python创建随机用户名密码并存放于Access数据库

    利用random库随机生成4到32位包含字母跟数字的用户名密码,利用win32com库连接到access数据库并写入table,要更改创建的用户名密码数量修改18行代码的数字即可. import wi ...

  8. Maven打包后的文件存在中文乱码

    发现打包的js文件虽然是UTF-8格式的编码,但是有中文有乱码 可设置jvm的编码,两种方法: 在系统的环境变量中添加一个变量,名为: JAVA_TOOL_OPTIONS, 值为:-Dfile.enc ...

  9. PeopleSoft通过status汇总进程运行情况

    下面SQL可以按照run_status汇总进程的运行状态 SELECT RQST.RUNSTATUS, RQST.PRCSTYPE, (SELECT XLAT.XLATLONGNAME FROM PS ...

  10. C# webapi简单学习

    创建WebApi项目: 在VS工具中创建一个ASP.NET Web应用程序 选择Webapi 一个webapi项目就创建好了 这里简单的写一个post和get两种请求的方法,由于post请求参数需要参 ...