Java日志体系(commons-logging)

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,则不能获取具体的日志工厂对象,则导致启动失败,这样就是我们常说的--动态查找机制。

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

推荐阅读更多精彩内容

  • 3 slf4j 3.1 简介 与commons-logging相同,slf4j也是一个通用的日志接口,在程序中与其...
    贾博岩阅读 2,751评论 0 7
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • 前言 最近学习开java web服务器开发,开始学习java,处理业务逻辑,但对其中的日志比较好奇,之前没怎么接触...
    九风萍舟阅读 3,283评论 1 6
  • 爆款产品犹如一把利剑,出鞘时闪的光,折射的都是打磨时的泪。 本期局内人: 李海波(喜马拉雅副总裁) 周志鹏(洛客联...
    约局阅读 291评论 0 0
  • 昨天大暑,夏季已进入最恐怖的“蒸笼”模式,到处都是潮热难耐。 中午儿子带上潜水装备找同学,想跟小伙伴一起游泳消暑。...
    三伊阅读 309评论 0 1