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/

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

  • 相关阅读:
    vue-fullcalendar插件
    iframe 父框架调用子框架的函数
    关于调试的一点感想
    hdfs 删除和新增节点
    hadoop yarn 实战错误汇总
    Ganglia 安装 No package 'ck' found
    storm on yarn(CDH5) 部署笔记
    spark on yarn 安装笔记
    storm on yarn安装时 提交到yarn失败 failed
    yarn storm spark
  • 原文地址:https://www.cnblogs.com/binarylei/p/10781582.html
Copyright © 2011-2022 走看看