zoukankan      html  css  js  c++  java
  • Java 日志体系(二)jcl 和 slf4j

    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 为例:

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    

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

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

    2.2 commons-logging 绑定日志实现

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

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

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

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

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

    commons-logging

    下面来详细说明:

    2.2.1 获取 LogFactory 的过程

    从下面几种途径来获取 LogFactory

    (1) 系统属性中获取

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

    (2) 使用 java 的 SPI 机制

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

    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

    LogFactoryImpl 是 commons-logging 提供的默认实现
    

    2.2.2 根据 LogFactory 获取 Log 的过程

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

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

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

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

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

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

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

    classesToDiscover 属性值如下:

    private static final String[] classesToDiscover = {
        "org.apache.commons.logging.impl.Log4JLogger",
        "org.apache.commons.logging.impl.Jdk14Logger",
        "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
        "org.apache.commons.logging.impl.SimpleLog"
    };
    

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

    三、slf4j

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

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

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

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

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

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

    // getLogger -> getILoggerFactory -> performInitialization -> bind -> StaticLoggerBinder.getSingleton()
    public static ILoggerFactory getILoggerFactory() {
        // 1. 初始化日志系统,调用 performInitialization -> bind -> StaticLoggerBinder.getSingleton()
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        // 2. 日志系统初始化成功,则返回 StaticLoggerBinder.getSingleton().getLoggerFactory()
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        // 3. 没有 StaticLoggerBinder 则不输出任何日志
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }
    

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

    public ILoggerFactory getLoggerFactory() {
        if (!initialized) {
            return defaultLoggerContext;
        }
    
        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        }
        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }
    

    参考:

    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/

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

  • 相关阅读:
    mysql 取年、月、日、时间
    第4步:创建RAC共享磁盘组
    第3步:添加用户与安装路径
    第2步:配置系统安装环境
    第1步:安装虚拟机+配置网络
    nodejs rar/zip加密压缩、解压缩
    使用shell脚本守护node进程
    抒发一下这些天用django做web项目的一些体会
    编写gulpfile.js文件:压缩合并css、js
    在NodeJS中使用流程控制工具Async
  • 原文地址:https://www.cnblogs.com/binarylei/p/10781582.html
Copyright © 2011-2022 走看看