Java日志体系(commons-logging)Java日志系统学习

Java日志系统学习

作为一名Java开发者,日志是我们工作中经常接触到的一项技术。对于Web应用而言,日志的重要性不言而喻,是必不可少的一部分;日志提供了丰富的记录功能,例如程序运行时的错误信息,描述信息,状态信息和执行时间信息等。

在实际生产环境中,日志是查找问题的重要来源,良好的日志格式和记录可以帮助Developer快速定位到错误的根源,找到问题的原因;

尽管对于现在的应用程序来说,日志至关重要,但是在JDK最初的版本当中并不包含日志记录的API和实现,直到JDK1.4后才被加入;因此,开源社区在此期间提供了众多贡献,其中名声最大、运用最广泛的当log4j莫属,当然后续的logback、log4j2也在迅速的普及;下面,就让笔者来进行具体的介绍。

1 commons-logging

1.1 简介

Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,它的出现避免了和具体的日志方案直接耦合;在日常开发中,developer可以选择第三方日志组件进行搭配使用,例如log4j、logback等;

说的直白些,commons-logging提供了操作日志的接口,而具体实现交给log4j、logback这样的开源日志框架来完成;这样的方式,实现了程序的解耦,对于底层日志框架的改变,并不会影响到上层的业务代码。

1.2 commons-logging结构

Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error

LogFactory:抽象类,日志工厂,获取日志类;

LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;

Log4JLogger:对log4j的日志对象封装;

Jdk14Logger:对JDK1.4的日志对象封装;

Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;

SimpleLog:commons-logging自带日志对象;

1.3 使用

commons-logging的使用非常简单。首先,需要在pom.xml文件中添加依赖:

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

声明测试代码:

public class commons_loggingDemo {
    Log log= LogFactory.getLog(commons_loggingDemo.class);
    @Test
    public void test() throws IOException {
        log.debug("Debug info.");
        log.info("Info info");
        log.warn("Warn info");
        log.error("Error info");
        log.fatal("Fatal info");
    }
}

接下来,在classpath下定义配置文件:commons-logging.properties:

#指定日志对象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工厂:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl

在我们的项目中,如果只单纯的依赖了commons-logging,那么默认使用的日志对象就是Jdk14Logger,默认使用的日志工厂就是LogFactoryImpl

1.4 源码分析

public abstract class LogFactory {
    public static final String HASHTABLE_IMPLEMENTATION_PROPERTY="org.apache.commons.logging.LogFactory.HashtableImpl";
    
    private static final String WEAK_HASHTABLE_CLASSNAME = "org.apache.commons.logging.impl.WeakHashtable";

    public static final String FACTORY_PROPERTIES = "commons-logging.properties";

    public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";

    public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl";

    //LogFactory静态代码块:  
    static {
        //获取LogFactory类加载器:AppClassLoader
        thisClassLoader = getClassLoader(LogFactory.class);
        String classLoaderName;
        try {
            ClassLoader classLoader = thisClassLoader;
            if (thisClassLoader == null) {
                classLoaderName = "BOOTLOADER";
            } else {
                //获取classLoader的名称:sun.misc.Launcher$AppClassLoader@150838093
                classLoaderName = objectId(classLoader);
            }
        } catch (SecurityException e) {
            classLoaderName = "UNKNOWN";
        }
        diagnosticPrefix = "[LogFactory from " + classLoaderName + "] ";
        diagnosticsStream = initDiagnostics();
        logClassLoaderEnvironment(LogFactory.class);
        //创建存放日志的工厂缓存对象:实际为org.apache.commons.logging.impl.WeakHashtable
        factories = createFactoryStore();
        if (isDiagnosticsEnabled()) {
            logDiagnostic("BOOTSTRAP COMPLETED");
        }
    }

    //获取日志对象:
    public static Log getLog(Class clazz) throws LogConfigurationException {
        //得到LogFactoryImpl日志工厂后,实例化具体的日志对象:
        return getFactory().getInstance(clazz);
    }
    //获取日志工厂
    public static LogFactory getFactory() throws LogConfigurationException {
        //获取当前线程的classCloader:
        ClassLoader contextClassLoader = getContextClassLoaderInternal();
        if (contextClassLoader == null) {
            .....
        }
        //从缓存中获取LogFactory:此缓存就是刚才在静态代码块中创建的WeakHashtable
        LogFactory factory = getCachedFactory(contextClassLoader);
        //如果存在就返回:
        if (factory != null) {
            return factory;
        }
        if (isDiagnosticsEnabled()) {
            ......
        }
        //读取classpath下的commons-logging.properties文件:
        Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
        ClassLoader baseClassLoader = contextClassLoader;
        if (props != null) {
            //如果Properties对象不为空,从中获取 TCCL_KEY 的值:
            String useTCCLStr = props.getProperty(TCCL_KEY);
            if (useTCCLStr != null) {
                if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
                    baseClassLoader = thisClassLoader;
                }
            }
        }
        .....
        try {
            /从系统属性中获取 FACTORY_PROPERTY 的值:
            String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
            if (factoryClass != null) {
                //如果该值不为空,则实例化日志工厂对象:
                factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
            } else {
                .....
            }
        } catch (SecurityException e) {
           .....
        }
        if (factory == null) {
            if (isDiagnosticsEnabled()) {
                ....
            }
            try {
                //如果日志工厂对象还为null,则从 META-INF/services/org.apache.commons.logging.LogFactory 中获取:
                final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
                if( is != null ) {
                    .....
                }
            } catch (Exception ex) {
                ......
            }
        }
        if (factory == null) {
            if (props != null) {
                //如果此时日志工厂为null,并props有值,则获取 FACTORY_PROPERTY 为key的值:
                String factoryClass = props.getProperty(FACTORY_PROPERTY);
                if (factoryClass != null) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic(
                            "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
                    }
                    //如果我们配置了,则实例化我们配置的日志工厂对象
                    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
                } else {
                    .....
                }
            } else {
                ......
            }
        }
        if (factory == null) {
            if (isDiagnosticsEnabled()) {
               .....
            }
            //如果此时日志工厂依旧为null,则实例化默认工厂:org.apache.commons.logging.impl.LogFactoryImpl
            factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
        }
        if (factory != null) {
            // 将日志工厂添加到缓存当中:
            cacheFactory(contextClassLoader, factory);
            if (props != null) {
                Enumeration names = props.propertyNames();
                while (names.hasMoreElements()) {
                    String name = (String) names.nextElement();
                    String value = props.getProperty(name);
                    factory.setAttribute(name, value);
                }
            }
        }
        return factory;
    }

    //创建存放日志的工厂缓存对象:
    private static final Hashtable createFactoryStore() {
        Hashtable result = null;
        String storeImplementationClass;
        try {
            //从系统属性中获取 HASHTABLE_IMPLEMENTATION_PROPERTY 为key的值:
            storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null);
        } catch (SecurityException ex) {
            storeImplementationClass = null;
        }
        //如果 storeImplementationClass 为null
        if (storeImplementationClass == null) {
            //将 WEAK_HASHTABLE_CLASSNAME 赋值给storeImplementationClass字符串
            storeImplementationClass = WEAK_HASHTABLE_CLASSNAME;
        }
        try {
            //反射实例化缓存对象:org.apache.commons.logging.impl.WeakHashtable
            Class implementationClass = Class.forName(storeImplementationClass);
            result = (Hashtable) implementationClass.newInstance();
        } catch (Throwable t) {
            .....
        }
        if (result == null) {
            result = new Hashtable();
        }
        return result;
    }
}

public class LogFactoryImpl extends LogFactory {

    protected Hashtable instances = new Hashtable();

    protected Constructor logConstructor = null;
    
    public Log getInstance(Class clazz) throws LogConfigurationException {
        return getInstance(clazz.getName());
    }
    public Log getInstance(String name) throws LogConfigurationException {
        //从缓存中获取日志对象:
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            //创建日志对象:
            instance = newInstance(name);
            instances.put(name, instance);
        }
        return instance;
    }
    protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            //日志构造器对象为null:
            if (logConstructor == null) {
                instance = discoverLogImplementation(name);
            }else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }

            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }
            return instance;
        } catch (LogConfigurationException lce) {
           ....
        }
    }
    //发现日志实现类:
    private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {
        if (isDiagnosticsEnabled()) {
            logDiagnostic("Discovering a Log implementation...");
        }
        initConfiguration();
        Log result = null;
        
        //从classpath中的commons-logging.properties文件中获取“org.apache.commons.logging.Log”的value:
        String specifiedLogClassName = findUserSpecifiedLogClassName();
        
        //如果配置文件中存在,那么就进行日志对象实例化:
        if (specifiedLogClassName != null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Attempting to load user-specified log class '" + specifiedLogClassName + "'...");
            }
            //配置的日志对象必须存在,否则报错:
            result = createLogFromClass(specifiedLogClassName, logCategory, true);
            if (result == null) {
               .......
            }
            return result;
        }
        if (isDiagnosticsEnabled()) {
            ....
        }
        /*
          *如果配置文件中不存在“org.apache.commons.logging.Log”的value:
          *那么还有遍历classesToDiscover字符串数组,进行实例化:
          *此数组中包括:log4j、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog
        */
        for(int i=0; i<classesToDiscover.length && result == null; ++i) {
            result = createLogFromClass(classesToDiscover[i], logCategory, true);
        }
        //如果此时日志对象还未null,为报错;
        if (result == null) {
            throw new LogConfigurationException("No suitable Log implementation");
        }
        //返回日志对象:
        return result;
    }
}

以上就是commons-logging获取日志对象的全过程,具体文字总结如下:

获取当前线程的classLoader,根据classLoader从缓存中获取LogFactroy,使用的缓存是WeakHashTable对象;如果缓存中存在,则返回,没有则进入下面流程;

读取classpath下的commons-logging.properties文件,判断其中是否设置了use_tccl属性,如果不为空则判断,该属性的值是否为false,若为false,则将baseClassLoader替换为当前类的classLoader;

接着,继续获取LogFactory对象,此步骤分为4中方式:
    (1)在系统属性中查找“org.apache.commons.logging.LogFactory”属性的值,根据值生成LogFactory对象;
    (2)通过资源“META-INF/services/org.apache.commons.logging.LogFactory”文件,获取的值生成LogFactory对象;
    (3)通过配置文件commons-logging.properties,获取以“org.apache.commons.logging.LogFactory”为key的值,根据值生成logFactory;
    (4)如果以上均不成功,则创建系统默认的日志工厂:org.apache.commons.logging.impl.LogFactoryImpl

成功获取日志工厂后,根据类名获取日志对象;

主要逻辑在discoverLogImplementation方法中:
    (1)检查commons-logging.properties文件中是否存在“org.apache.commons.logging.Log”属性,若存在则创建具体的日志对象;若不存在,进行下面逻辑;
    (2)遍历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;
    (3)根据数组中存着的全限定类名,按照顺序依次加载Class文件,进行实例化操作,最后返回Log实例,默认为Jdk14Logger;

其中,获取日志工厂的过程,诟病最多。究其原因,主要是commons-logging在获取日志工厂的过程中使用了classLoader来寻找日志工厂实现,进而导致了其他组件,如若使用自己的classload,则不能获取具体的日志工厂对象,则导致启动失败,这样就是我们常说的--动态查找机制。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿天地

用aop加redis实现通用接口缓存

系统在高并发场景下,最有用的三个方法是缓存,限流,降级。 缓存就是其中之一,目前缓存基本上是用redis或者memcached。 redis和memcached...

3547
来自专栏Java编程技术

SpringBoot之class is not visible from class loader

最近在搭建SpringBoot的新应用,遇到个有意思的问题,如题就是在加载某一个类时候抛出了class is not visible from class lo...

1242
来自专栏码匠的流水账

spring security运行时配置ignore url

以前用shiro的比较多,不过spring boot倒是挺推崇自家的spring security的,有默认的starter,于是也就拿来用了。

1051
来自专栏Java架构师学习

带你深入了解Java线程中的那些事

引言 说到Thread大家都很熟悉,我们平常写并发代码的时候都会接触到,那么我们来看看下面这段代码是如何初始化以及执行的呢? public class Thre...

3368
来自专栏纯洁的微笑

Guava 源码分析(Cache 原理【二阶段】)

在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理。

1811
来自专栏大内老A

IoC+AOP的简单实现

对EnterLib有所了解的人应该知道,其中有一个名叫Policy Injection的AOP框架;而整个EnterLib完全建立在另一个叫作Unity的底层框...

2139
来自专栏Seebug漏洞平台

Jenkins 未授权远程代码执行漏洞(CVE-2017-1000353)

漏洞概要 Jenkins 未授权远程代码执行漏洞, 允许攻击者将序列化的Java SignedObject对象传输给Jenkins CLI处理,反序列化Obj...

4176
来自专栏JackieZheng

照虎画猫写自己的Spring——依赖注入

前言 上篇《照虎画猫写自己的Spring》从无到有讲述并实现了下面几点 声明配置文件,用于声明需要加载使用的类 加载配置文件,读取配置文件 解析配置文件,需要将...

2038
来自专栏SDNLAB

Open vSwitch系列之openflow版本兼容

众所周知Open vSwitch支持的openflow版本从1.0到1.5版本(当前Open vSwitch版本是2.3.2)通过阅读代码,处理openflow...

57313
来自专栏IT杂记

通过Java程序提交通用Mapreduce任务并获取Job信息

背景 我们的一个业务须要有对MR任务的提交和状态跟踪的功能,须要通过Java代码提交一个通用的MR任务(包括mr的jar、配置文件、依赖的第三方jar包),并且...

1.1K5

扫码关注云+社区

领取腾讯云代金券