Java门面日志commons-logging

1 简介

Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,也被称为“门面日志”。“门面日志”利用了设计模式中的门面模式思想,对外提供一套通用的日志记录的 API,而不提供具体的日志输出服务,如果要实现日志输出,需要集成其他的日志框架,比如 Log4j、Logback、Log4j2 等。

这种门面模式的好处在于,记录日志的 API 和日志输出的服务分离开,代码里面只需要关注记录日志的 API,通过 SLF4J 指定的接口记录日志;而日志输出通过引入 JAR 包的方式即可指定其他的日志框架。当我们需要改变系统的日志输出服务时,不用修改代码,只需要改变引入日志输出框架 JAR 包。

JUL最后更新于2014年7月,现以停止更新。

image

2 包结构

image
  • Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error
  • LogFactory:抽象类,日志工厂,获取日志类;
  • LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;
  • Log4JLogger:对log4j的日志对象封装;
  • Jdk14Logger:对JDK1.4的日志对象封装;
  • Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;
  • SimpleLog:commons-logging自带日志对象;

3 日志加载

jcl可以通过在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

jcl如果没有指定日志实现框架则默认加载时按照顺序log4j>jul> simpleLog依次加载,如果查找到log4j包则使用log4j,如果没有依次类推。

4 JCL使用

引用JCL依赖包

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

测试代码

此时由于项目也没有放入其他日志框架依赖包,会按照顺序log4j>jul> simpleLog加载,因此此时会使用jdk自带jul实现日志打印。

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");
    }
}

引用log4j依赖包

在不修改代码的前提下,引入log4j依赖包。在次调用测试代码,此时会使用log4j实现日志打印。

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

5 JCL原理分析

5.1 LogFactory 类初始化
   protected static Hashtable factories = null;
   
   private static PrintStream diagnosticsStream = null;

   static {
        /** 获取LogFactory类加载器:AppClassLoader设置到thisClassLoader **/
        thisClassLoader = getClassLoader(LogFactory.class);
        String classLoaderName;
        try {
            ClassLoader classLoader = thisClassLoader;
            if (thisClassLoader == null) {
                classLoaderName = "BOOTLOADER";
            } else {
                classLoaderName = objectId(classLoader);
            }
        } catch (SecurityException e) {
            classLoaderName = "UNKNOWN";
        }
        diagnosticPrefix = "[LogFactory from " + classLoaderName + "] ";

        /** 初始化diagnosticsStream,本质实现为PrintStream负责打印输出**/
        diagnosticsStream = initDiagnostics();

        /** 获取LogFactory.class的类加载器,并通过diagnosticsStream打印 **/
        logClassLoaderEnvironment(LogFactory.class);

        /** 实例化存放日志的工厂缓存对象:默认实现为org.apache.commons.logging.impl.WeakHashtable **/
        factories = createFactoryStore();
        if (isDiagnosticsEnabled()) {
            logDiagnostic("BOOTSTRAP COMPLETED");
        }
    }

实例化存放日志的工厂缓存对象

private static final Hashtable createFactoryStore() {
        Hashtable result = null;
        String storeImplementationClass;
        /** 从系统属性中获取工厂缓存的实现类**/
        try {
            storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null);
        } catch (SecurityException ex) {
            storeImplementationClass = null;
        }

        /** 从WEAK_HASHTABLE_CLASSNAME获取工厂缓存的默认实现类**/
        if (storeImplementationClass == null) {
            storeImplementationClass = WEAK_HASHTABLE_CLASSNAME;
        }

        /** 实例化工厂缓存实现类对象 **/
        try {
            Class implementationClass = Class.forName(storeImplementationClass);
            result = (Hashtable) implementationClass.newInstance();
        } catch (Throwable t) {
            handleThrowable(t); // may re-throw t
            if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed");
                } else {
                    System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
                }
            }
        }
        if (result == null) {
            result = new Hashtable();
        }
        /** 返回 结果**/
        return result;
    }
5.2 从LogFactory获取Log对象
    /**
     * 获取日志对象
     */
    public static Log getLog(Class clazz) throws LogConfigurationException {
        /** 得到LogFactoryImpl日志工厂后,实例化具体的日志对象:**/
        return getFactory().getInstance(clazz);
    }

    /**
     * 获取日志对象
     */
    public static Log getLog(String name) throws LogConfigurationException {
        /** 得到LogFactoryImpl日志工厂后,实例化具体的日志对象:**/
        return getFactory().getInstance(name);
    }

5.3 获取并实例化LogFactory
 public static LogFactory getFactory() throws LogConfigurationException {
        /** 获取上下文类加载器 **/
        ClassLoader contextClassLoader = getContextClassLoaderInternal();

        if (contextClassLoader == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Context classloader is null.");
            }
        }

        /** 从缓存factories中获取LogFactory **/
        LogFactory factory = getCachedFactory(contextClassLoader);
        if (factory != null) {
            return factory;
        }

        if (isDiagnosticsEnabled()) {
            logDiagnostic(
                    "[LOOKUP] LogFactory implementation requested for the first time for context classloader " +
                    objectId(contextClassLoader));
            logHierarchy("[LOOKUP] ", contextClassLoader);
        }

        /** 读取classpath下的commons-logging.properties文件:**/
        Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);

        /**
         * 读取commons-logging.properties配置文件中use_tccl属性值
         * 如果属性值存在且为flase,设置baseClassLoader指向thisClassLoader,
         * 如果不存在use_tccl属性值或属性值为true,baseClassLoader指向上下文类加载器
         * **/
        ClassLoader baseClassLoader = contextClassLoader;
        if (props != null) {
            String useTCCLStr = props.getProperty(TCCL_KEY);
            if (useTCCLStr != null) {
                if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
                    baseClassLoader = thisClassLoader;
                }
            }
        }

        if (isDiagnosticsEnabled()) {
            logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
                          "] to define the LogFactory subclass to use...");
        }

        /**
         * 读取commons-logging.properties配置文件中org.apache.commons.logging.LogFactory属性值,作为LogFactory实现类,同时实例化LogFactory对象
         **/
        try {
            String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
            if (factoryClass != null) {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
                                  "' as specified by system property " + FACTORY_PROPERTY);
                }
                factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
            } else {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
                }
            }
        } catch (SecurityException e) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +
                              " instance of the custom factory class" + ": [" + trim(e.getMessage()) +
                              "]. Trying alternative implementations...");
            }
        } catch (RuntimeException e) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] An exception occurred while trying to create an" +
                              " instance of the custom factory class" + ": [" +
                              trim(e.getMessage()) +
                              "] as specified by a system property.");
            }
            throw e;
        }

        /**
         * 读取META-INF/services/org.apache.commons.logging.LogFactory配置文件中数据,作为LogFactory实现类,同时实例化LogFactory对象
         **/
        if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
                              "] to define the LogFactory subclass to use...");
            }
            try {
                final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);

                if( is != null ) {
                    BufferedReader rd;
                    try {
                        rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                    } catch (java.io.UnsupportedEncodingException e) {
                        rd = new BufferedReader(new InputStreamReader(is));
                    }

                    String factoryClassName = rd.readLine();
                    rd.close();

                    if (factoryClassName != null && ! "".equals(factoryClassName)) {
                        if (isDiagnosticsEnabled()) {
                            logDiagnostic("[LOOKUP]  Creating an instance of LogFactory class " +
                                          factoryClassName +
                                          " as specified by file '" + SERVICE_ID +
                                          "' which was present in the path of the context classloader.");
                        }
                        factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
                    }
                } else {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
                    }
                }
            } catch (Exception ex) {

                if (isDiagnosticsEnabled()) {
                    logDiagnostic(
                        "[LOOKUP] A security exception occurred while trying to create an" +
                        " instance of the custom factory class" +
                        ": [" + trim(ex.getMessage()) +
                        "]. Trying alternative implementations...");
                }
            }
        }

        /** 在次读取commons-logging.properties配置文件中org.apache.commons.logging.LogFactory属性值,作为LogFactory实现类,同时实例化LogFactory对象 **/
        if (factory == null) {
            if (props != null) {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic(
                        "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +
                        "' to define the LogFactory subclass to use...");
                }
                String factoryClass = props.getProperty(FACTORY_PROPERTY);
                if (factoryClass != null) {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic(
                            "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
                    }
                    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

                    // TODO: think about whether we need to handle exceptions from newFactory
                } else {
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
                    }
                }
            } else {
                if (isDiagnosticsEnabled()) {
                    logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");
                }
            }
        }

        /**  如果上诉操作没有获取factory实现对象,则使用org.apache.commons.logging.impl.LogFactoryImpl作为默认日志工厂实现类,并实例化工厂对象 **/
        if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic(
                    "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +
                    "' via the same classloader that loaded this LogFactory" +
                    " class (ie not looking in the context classloader).");
            }
            factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
        }

        /** 将LogFactory添加到缓存factories当中: **/
        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;
    }
5.4 从日志工厂获取日志对象:
    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 Constructor logConstructor = null;

    /**
     * The signature of the Constructor to be used.
     */
    protected Class logConstructorSignature[] = { String.class };

    /**
     * 实例化日志对象反相依赖日志工厂的方法<code>setLogFactory</code>
     */
    protected Method logMethod = null;
    
    protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            /** 如果构造器不存在,调用discoverLogImplementation尝试通过类名称实例化日志对象 **/
            if (logConstructor == null) {
                instance = discoverLogImplementation(name);
            }
            /** 如果构造器存在,使用构造器实例化日志对象 **/
            else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }
            /** 如果实例化日志对象,存在setLogFactory方法,则调用和构日志工厂方法反相关联 **/
            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }

            return instance;

        } catch (LogConfigurationException lce) {
            throw lce;
        } catch (InvocationTargetException e) {
            Throwable c = e.getTargetException();
            throw new LogConfigurationException(c == null ? e : c);
        } catch (Throwable t) {
            handleThrowable(t); 
            throw new LogConfigurationException(t);
        }
    }
    
    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) {
                StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");
                messageBuffer.append(specifiedLogClassName);
                messageBuffer.append("' cannot be found or is not useable.");

                // Mistyping or misspelling names is a common fault.
                // Construct a good error message, if we can
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
                throw new LogConfigurationException(messageBuffer.toString());
            }

            return result;
        }

        if (isDiagnosticsEnabled()) {
            logDiagnostic(
                "No user-specified Log implementation; performing discovery" +
                " using the standard supported logging implementations...");
        }
        /**
         *如果配置文件中不存在“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;

6 缺陷

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容