03、JCL(日志门面)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 03、JCL(日志门面)

一、认识JCL


1.1、JCL概述


JCL(全称为Jakarta Commons Logging):是Apache提供的一个通用日志API,它为多个Java日志框架实现(如JUL、Log4j等)提供了一个统一的接口Log,其自身也提供一个日志实现SimpleLog(一般不使用它)。


在2014年的时候被Apache淘汰了(具有缺陷,当时只考虑了主流的应用框架,对于未来新出现框架不提供相应的实现类实现,除非自己修改源码进行完善),最新的版本为1.2(即最终版本,停止维护),后期又推出了更加优秀的日志门面框架。

JCL包含两个基本抽象类:Log类(通用接口)、LogFactory(负责创建Log实例)。


首先看下面这个图:



这个图只是个抽象的举例,在JCL中提供了一个通用接口Log(包含多种日志级别方法),该Log接口有多个实现类其中的实现方法分别使用了不同日志框架的日志方法,这种方式解决了什么问题呢?


能够让我们在系统进行升级时更好的切换使用日志框架,例如通过JCL日志门面我们默认使用的是JUL,若是想要替换使用Log4j,则只需要导入Log4j的jar包并提供配置文件,不用修改方法即可使用。


1.2、第三方jar包


想要使用JCL,我们需要引入第三方jar包,下面给出pom.xml的坐标:


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



1.3、认识Log接口与LogFactory抽象类


Log接口



可以看到Log接口中都是对应的日志等级方法。

右边则是Log接口的继承图,红框中的都是它的实现类,其中Jdk14Logger(其中Logger实例使用的JUL)、Log4JLogger(使用的是Log4j)


LogFactory抽象类



我们需要关注其getLog()方法,获取Log实例是通过该工厂类来获取的。


二、JCL实际使用


2.1、应用JUL(jdk自带日志)


首先引入jar包,接着通过使用LogFactory获取到Log实例,调用日志方法即可:


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}



通过输出的内容我们可以判定是JUL默认输出的格式内容。

说明:在不导入其他指定日志框架时(如Log4j),通过工厂类获取到的Log实例其实就是Jdk14Logger实例(方法实际上调用的就是JUL的日志方法),我们之后看源码即可知晓。



2.2、应用Log4j(第三方)


想要从JUL切换到Log4j,我们只需要简单一步,导入Log4j的jar包即可,引入坐标:


<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>


添加配置文件log4j.properties,若是maven项目放置到resource目录下:


# rootLogger日志等级为trace,输出到屏幕上
log4j.rootLogger = trace,console
# console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n


测试程序:


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}



可以看到使用相同的方法,随着我们的jar包引入切换使用为Log4j的日志方法,是不是很妙。之后我们看其中源码即可知道究竟是如何实现了的。



三、源码分析


3.1、Log接口实现类分析


我们知道Log接口有多个实现类,其实现类的方法即调用的是各个日志框架的方法,我们深入源码查看一下JUL、Log4j的日志方法是如何被应用到JCL的实现类中的:


Jdk14Logger实现类



我们注重看其中的构造器以及部分日志等级方法:


//注意这里引入的类是JDK自带的日志JUL的Logger类
import java.util.logging.Level;
import java.util.logging.Logger;
public class Jdk14Logger implements Log, Serializable {
    protected static final Level dummyLevel = Level.FINE;
    protected transient Logger logger = null;//默认为null
    public Jdk14Logger(String name) {
        this.name = name;
        //调用了自身的getLogger()方法
        logger = getLogger();//见1
    }
    //1、获取Logger实例方法
    public Logger getLogger() {
        if (logger == null) {
            //重要:这里调用的是java.util.logging.Logger(即JUL)的获取实例方法
            logger = Logger.getLogger(name);
        }
        return logger;
    }
    //info日志等级调用
    public void info(Object message) {
        //实际上就是调用的JUL的INFO日志等级的Log方法
        log(Level.INFO, String.valueOf(message), null);
    }
    //fatal:我们之前在Log4j中看到是最高等级,这里也用来表示最高的意思
    public void fatal(Object message, Throwable exception) {
        //实际上就是调用JUL的SERVER日志等级(最高)的Log方法
        log(Level.SEVERE, String.valueOf(message), exception);
    }
}


看到这里我们其实心里就清楚了,原来就是外面套了个方法呀,通过继承Log接口来让Log接口统一管理各个日志框架,从而达到切换日志框架不改变代码的操作。那对于JCL中Log4j的方法我们也差不多知晓了是如何实现了的。



Log4JLogger实现类



老样子都实现了Log接口的日志等级方法

直接看其中构造器以及相关方法还有静态代码块:


//注意这里使用的是Log4j的日志
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.apache.log4j.Level;
public class Log4JLogger implements Log, Serializable {
        //默认为null
  private transient volatile Logger logger = null;
      private static final String FQCN = Log4JLogger.class.getName();
        //类被加载时进行初始化
        static {
            if (!Priority.class.isAssignableFrom(Level.class)) {
                throw new InstantiationError("Log4J 1.2 not available");
            }
            Priority _traceLevel;
            try {
            } catch(Exception ex) {
                _traceLevel = Level.DEBUG;
            }
            traceLevel = _traceLevel;
        }
        //无参构造器
        public Log4JLogger() {
            name = null;
        }
        //有参构造器
        public Log4JLogger(String name) {
            this.name = name;
            //通过调用自身类的getLogger()获取
            this.logger = getLogger();//见1
        }
       //1、通过org.apache.log4j.Logger的getLogger()来获取实例
       public Logger getLogger() {
            Logger result = logger;
            if (result == null) {
                synchronized(this) {
                    result = logger;
                    if (result == null) {
                        logger = result = Logger.getLogger(name);
                    }
                }
            }
            return result;
        }
        //fatal等级方法就是调用的Log4j的FATAL等级,是一致的
        public void fatal(Object message) {
            getLogger().log(FQCN, Level.FATAL, message, null);
        }
}


我们可以看到当获取该实现类的实例对象时,做了获取Log4j的Logger实例操作,其中的日志等级方法中实际就是调用的Log4j的日志方法。

注意:看其中的静态方法就有一个Level.DEBUG;获取操作,我们可以试想一下若是没有引入Log4j的jar包,那么该操作也不会实现即可报错,也是一种证实开发者没有导入Log4j的操作(这里只是我的设想)。



3.2、JCL原理分析(动态加载Log实现类)


分析下面的第7行程序段:


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}


源码分析:


public abstract class LogFactory {
    //1、实际调用了其实现类的方法
    public static Log getLog(Class clazz) throws LogConfigurationException {
        //获取LogFactoryImpl
        return getFactory().getInstance(clazz);//见2
    }
}
public class LogFactoryImpl extends LogFactory {
    protected Constructor logConstructor = null;
    //该数组中包含了对应的全限定类名
    private static final String[] classesToDiscover = {
            LOGGING_IMPL_LOG4J_LOGGER,//"org.apache.commons.logging.impl.Log4JLogger"
            "org.apache.commons.logging.impl.Jdk14Logger",
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
            "org.apache.commons.logging.impl.SimpleLog"
    };
    //2、获取Log实例
    public Log getInstance(Class clazz) throws LogConfigurationException {
        //这里传入类的名称
        return getInstance(clazz.getName());//见3
    }
    //3、其中获取到Log实例
    public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            //通过类名获取到Log实例
            instance = newInstance(name);//见4
            instances.put(name, instance);
        }
        return instance;
    }
    //4、获取新的实例
    protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            //默认为null
            if (logConstructor == null) {
                //重要:查找Log实现类,及获取Log的单个实现类
                instance = discoverLogImplementation(name);//见5
            }
            else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }
            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }
            //返回获取到的Log实例即可
            return instance;
        } catch (LogConfigurationException lce) {
  ....
    }
    //5、该方法通过数组的顺序来进行实现类实例
    private Log discoverLogImplementation(String logCategory)
        throws LogConfigurationException {
        ...//无关紧要的先省略
        if (isDiagnosticsEnabled()) {
            logDiagnostic(
                "No user-specified Log implementation; performing discovery" +
                " using the standard supported logging implementations...");
        }
        //核心部分:从数组中进行遍历,可见classesToDiscover数组内容(上面),顺序依次为Log4JLogger、Jdk14Logger...
        //其中的result == null则是判断是否获取到了实例,若获取到退出for循环
        for(int i=0; i<classesToDiscover.length && result == null; ++i) {
            result = createLogFromClass(classesToDiscover[i], logCategory, true);
        }
        if (result == null) {
            throw new LogConfigurationException
                        ("No suitable Log implementation");
        }
  //返回指定实例
        return result;
    }


第38行:调用方法获取到Log的实现类,确定使用的日志框架。

第73行:核心代码,根据多种框架实现类的权限定类名去查找获取Log实例的。

说明:我们在调用LogFactory.getLog()方法时获取了JCL中Log接口的实现类实例,在实现类中的构造器里获取到了真正对应的日志框架Logger实例。


这样的话我们之前的第二部分就解释的通为什么默认获取到的是JUL,在添加了jar包之后则为Log4j了!!!



总结


1、JCL日志门面中Log是通用接口,LogFactory.getLog()用于获取对应Log的实现类。


2、Log接口(包含了主要的日志等级方法)的实现类主要的是两个,Jdk14Logger(即JUL,不导任何包默认)和Log4JLogger(即Log4j,导入Log4j则切换)。


3、对于获取Log实例是在LogFactory.getLog()中执行获取的,执行顺序见如下核心代码


private static final String[] classesToDiscover = {
    LOGGING_IMPL_LOG4J_LOGGER,//"org.apache.commons.logging.impl.Log4JLogger"
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};
//核心部分:从数组中进行遍历,可见classesToDiscover数组内容(上面),顺序依次为Log4JLogger、Jdk14Logger...
//其中的result == null则是判断是否获取到了实例,若获取到退出for循环
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}


4、在获取到指定Log实例时,即使用构造器会进行各个实现类中的Logger实例的赋值获取(通过对应的日志框架),其中的日志等级方法调用的是对应框架的日志等级方法。



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
Java 数据库连接 API
日志门面与日志实现框架介绍
日志门面与日志实现框架介绍
日志门面与日志实现框架介绍
|
Java API 开发者
04、slf4j(日志门面)(二)
04、slf4j(日志门面)(二)
04、slf4j(日志门面)(二)
|
Java API Maven
04、slf4j(日志门面)(一)
04、slf4j(日志门面)(一)
04、slf4j(日志门面)(一)
|
Java Apache
什么是日志门面? SpringBoot整合log4j2 ,日志落地
什么是日志门面? SpringBoot整合log4j2 ,日志落地
什么是日志门面? SpringBoot整合log4j2 ,日志落地
|
13天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
121 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
1月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
225 3
|
3月前
|
Kubernetes Ubuntu Windows
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
131 3
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1630 14
|
1月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
31 0
|
1月前
|
数据可视化
Tensorboard可视化学习笔记(一):如何可视化通过网页查看log日志
关于如何使用TensorBoard进行数据可视化的教程,包括TensorBoard的安装、配置环境变量、将数据写入TensorBoard、启动TensorBoard以及如何通过网页查看日志文件。
197 0