Java日志框架-commons-logging

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: > commons-logging是Apache commons类库中的一员。commons-logging自带了日志实现类,但是功能比较简单,更多的是将其作为门面,底层实现依赖其它框架。commons-logging能够选择使用Log4j还是JDK Logging,但是它并不依赖Log4j或JDK Logging的API。commons-logging会自动检测项目classpath中包含的支持

commons-logging是Apache commons类库中的一员。commons-logging自带了日志实现类,但是功能比较简单,更多的是将其作为门面,底层实现依赖其它框架。commons-logging能够选择使用Log4j还是JDK Logging,但是它并不依赖Log4j或JDK Logging的API。commons-logging会自动检测项目classpath中包含的支持的框架,从而自动选择实现框架。使用commons-logging能否灵活的选择使用哪些日志框架,而且不需要修改源代码。

项目地址

https://github.com/apache/commons-logging

使用示例

Log log = LogFactory.getLog(LoadTestCase.class);
log.info("test");

Log接口部分代码

/**
     * Logs a message with info log level.
     *
     * @param message log this message
     */
    void info(Object message);

    /**
     * Logs an error with info log level.
     *
     * @param message log this message
     * @param t log this cause
     */
    void info(Object message, Throwable t);

从接口定义可以看出,Log的info方法并不能像slf4j一样支持占位符,所以在使用时需要手动拼接字符串,该接口的其它方法一样不支持占位符。

源码解析

从第一行开始分析

public static Log getLog(Class clazz) throws LogConfigurationException {
        return getFactory().getInstance(clazz);
    }

LogFactory是个抽象类,其中有两个比较重要的抽象方法交由子类去实现

public abstract Log getInstance(Class clazz)
        throws LogConfigurationException;
        
public abstract Log getInstance(String name)
        throws LogConfigurationException;

可以看到getLog方法也调用了其中的抽象方法,我们先来看看getFactory()方法

public static LogFactory getFactory() throws LogConfigurationException {
        // Identify the class loader we will be using
        ClassLoader contextClassLoader = getContextClassLoaderInternal();

        if (contextClassLoader == null) {
            // This is an odd enough situation to report about. This
            // output will be a nuisance on JDK1.1, as the system
            // classloader is null in that environment.
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Context classloader is null.");
            }
        }

        // Return any previously registered factory for this class loader
        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);
        }

        // Load properties file.
        //
        // If the properties file exists, then its contents are used as
        // "attributes" on the LogFactory implementation class. One particular
        // property may also control which LogFactory concrete subclass is
        // used, but only if other discovery mechanisms fail..
        //
        // As the properties file (if it exists) will be used one way or
        // another in the end we may as well look for it first.

        Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);

        // Determine whether we will be using the thread context class loader to
        // load logging classes or not by checking the loaded properties file (if any).
        ClassLoader baseClassLoader = contextClassLoader;
        if (props != null) {
            String useTCCLStr = props.getProperty(TCCL_KEY);
            if (useTCCLStr != null) {
                // The Boolean.valueOf(useTCCLStr).booleanValue() formulation
                // is required for Java 1.2 compatibility.
                if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
                    // Don't use current context classloader when locating any
                    // LogFactory or Log classes, just use the class that loaded
                    // this abstract class. When this class is deployed in a shared
                    // classpath of a container, it means webapps cannot deploy their
                    // own logging implementations. It also means that it is up to the
                    // implementation whether to load library-specific config files
                    // from the TCCL or not.
                    baseClassLoader = thisClassLoader;
                }
            }
        }

        // Determine which concrete LogFactory subclass to use.
        // First, try a global system property
        if (isDiagnosticsEnabled()) {
            logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
                          "] to define the LogFactory subclass to use...");
        }

        try {
            // 1. 先从系统变量中看看是否定义了factory的实现类,由变量 org.apache.commons.logging.LogFactory定义
            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...");
            }
            // ignore
        } catch (RuntimeException e) {
            // This is not consistent with the behaviour when a bad LogFactory class is
            // specified in a services file.
            //
            // One possible exception that can occur here is a ClassCastException when
            // the specified class wasn't castable to this LogFactory type.
            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;
        }

        // Second, try to find a service by using the JDK1.3 class
        // discovery mechanism, which involves putting a file with the name
        // of an interface class in the META-INF/services directory, where the
        // contents of the file is a single line specifying a concrete class
        // that implements the desired interface.

        if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
                              "] to define the LogFactory subclass to use...");
            }
            try {
                // 2.看看文件META-INF/services/org.apache.commons.logging.LogFactory中是否定义了factory的实现类
                final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);

                if( is != null ) {
                    // This code is needed by EBCDIC and other strange systems.
                    // It's a fix for bugs reported in xerces
                    BufferedReader rd;
                    try {
                        rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                    } catch (java.io.UnsupportedEncodingException e) {
                        rd = new BufferedReader(new InputStreamReader(is));
                    }

                    String factoryClassName;
                    try {
                        factoryClassName = rd.readLine();
                    } finally {
                        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 {
                    // is == null
                    if (isDiagnosticsEnabled()) {
                        logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
                    }
                }
            } catch (Exception ex) {
                // note: if the specified LogFactory class wasn't compatible with LogFactory
                // for some reason, a ClassCastException will be caught here, and attempts will
                // continue to find a compatible class.
                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...");
                }
                // ignore
            }
        }

        // Third try looking into the properties file read earlier (if found)

        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...");
                }
                // 3.从配置文件commons-logging.properties中查找key为org.apache.commons.logging.LogFactory的value实例化factory
                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..");
                }
            }
        }

        // Fourth, try the fallback implementation class

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

            // Note: unlike the above code which can try to load custom LogFactory
            // implementations via the TCCL, we don't try to load the default LogFactory
            // implementation via the context classloader because:
            // * that can cause problems (see comments in newFactory method)
            // * no-one should be customising the code of the default class
            // Yes, we do give up the ability for the child to ship a newer
            // version of the LogFactoryImpl class and have it used dynamically
            // by an old LogFactory class in the parent, but that isn't
            // necessarily a good idea anyway.
            // 4. 使用默认的实现类org.apache.commons.logging.impl.LogFactoryImpl
            factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
        }

        if (factory != null) {
            /**
             * Always cache using context class loader.
             */
            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;
    }

从上面的代码分析可以知道commons-logging是按照如下顺序实例化Factory的

  1. 系统变量 org.apache.commons.logging.LogFactory定义
  2. 文件META-INF/services/org.apache.commons.logging.LogFactory定义
  3. 配置文件commons-logging.properties中查找key为org.apache.commons.logging.LogFactory定义
  4. 默认实现类org.apache.commons.logging.impl.LogFactoryImpl

假设使用了默认实现类,继续分析

先来看看默认实现类中对重要的两个抽象方法的实现

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 {
            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) {

            // this type of exception means there was a problem in discovery
            // and we've already output diagnostics about the issue, etc.;
            // just pass it on
            throw lce;

        } catch (InvocationTargetException e) {
            // A problem occurred invoking the Constructor or Method
            // previously discovered
            Throwable c = e.getTargetException();
            throw new LogConfigurationException(c == null ? e : c);
        } catch (Throwable t) {
            handleThrowable(t); // may re-throw t
            // A problem occurred invoking the Constructor or Method
            // previously discovered
            throw new LogConfigurationException(t);
        }
    }

默认情况下logConstructor为null,因此重点在于discoverLogImplementation方法

private Log discoverLogImplementation(String logCategory)
        throws LogConfigurationException {
        if (isDiagnosticsEnabled()) {
            logDiagnostic("Discovering a Log implementation...");
        }

        initConfiguration();

        Log result = null;

        // See if the user specified the Log implementation to use
        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...");
        }
        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;
    }

在findUserSpecifiedLogClassName方法中可以寻找用户自定义的Log实现类,此处不贴代码了,只说明寻找的顺序

  1. commons-logging.properties中org.apache.commons.logging.Log的定义
  2. commons-logging.properties中org.apache.commons.logging.log的定义(此为旧版本中的定义方式)
  3. 系统变量中org.apache.commons.logging.Log的定义
  4. 系统变量中org.apache.commons.logging.log的定义(此为旧版本中的定义方式)

以上为用户自定义Log实现类部分,如果找不到用户自定义Log实现类,则走默认的寻找逻辑。注意这段代码

private static final String[] classesToDiscover = {
            LOGGING_IMPL_LOG4J_LOGGER,
            "org.apache.commons.logging.impl.Jdk14Logger",
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
            "org.apache.commons.logging.impl.SimpleLog"
    };

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;

很清晰的看到commons-logging在寻找Log默认实现类时的寻找顺序

  1. Log4JLogger
  2. Jdk14Logger
  3. Jdk13LumberjackLogger
  4. SimpleLog

这几个Log实现类都是commons-logging自带的,它们都是org.apache.commons.logging.Log接口的实现类,而且都提供了一个只有一个String类型参数的构造函数,createLogFromClass方法中通过反射调用实现类的构造函数从而获得一个Log实例。

以Log4jLogger为例来说明commons-logging是如何委托给org.apache.log4j.Logger的

其实很简单,Log4jLogger本身持有了一个org.apache.log4j.Logger实例,它将所有的动作直接扔给这个org.apache.log4j.Logger实例去完成了。

自带Log实现类

commons-logging提供了两个有实际实现的Log实现类,其它的实现类则是委托给了具体的实现框架。这两个实现类比较简单,我们只是粗略看看,实际项目中不太可能用到它们。

SimpleLog

这是一个很简单的日志输出实现类,它只能控制台输出,提供了以下可定制的属性

  • org.apache.commons.logging.simplelog.showlogname(默认不输出)

是否输出Log名,就是传递给Simple构造函数的字符串

  • org.apache.commons.logging.simplelog.showShortLogname(默认输出)

是否输出短的Log名,一般传递给构造函数的字符串为全类名,例如com.google.test.Student,那么logname就是com.google.test.Student,而shortLogname就是Student,二者选一,不能同时输出

  • org.apache.commons.logging.simplelog.showdatetime

是否输出时间(默认不输出)

  • org.apache.commons.logging.simplelog.dateTimeFormat

输出的时间格式(默认为yyyy/MM/dd HH:mm:ss:SSS zzz)

这些变量的寻找顺序为

  1. 环境变量
  2. commons-logging.properties配置文件
NoOpLog

这个实现类什么都不做,所有的方法均为空

总结

commons-logging其主要作用是门面担当,更多的是依赖其它日志框架实现日志输出,当然commons-logging自身也提供了简单的实现类,但是功能单一,实际项目中基本上不会用到。建议大家在实际项目中还是选用更通用,更易用的slf4j-api作为门面。commons-logging相比slf4j-api的优势在于:它不依赖桥接器就能与其他日志框架配合使用。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3天前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
32 3
|
1天前
|
JSON Java fastjson
Java Http 接口对接太繁琐?试试 UniHttp 框架吧
UniHttp 是一个声明式的 HTTP 接口对接框架,旨在简化第三方 HTTP 接口的调用过程。通过注解配置,开发者可以像调用本地方法一样发起 HTTP 请求,无需关注请求的构建和响应处理细节。框架支持多种请求方式和参数类型,提供灵活的生命周期钩子以满足复杂的对接需求,适用于企业级项目的快速开发和维护。GitHub 地址:[UniAPI](https://github.com/burukeYou/UniAPI)。
|
5天前
|
人工智能 缓存 Java
深入解析Spring AI框架:在Java应用中实现智能化交互的关键
【10月更文挑战第12天】Spring AI 是 Spring 框架家族的新成员,旨在满足 Java 应用程序对人工智能集成的需求。它支持自然语言处理、图像识别等多种 AI 技术,并提供与云服务(如 OpenAI、Azure Cognitive Services)及本地模型的无缝集成。通过简单的配置和编码,开发者可轻松实现 AI 功能,同时应对模型切换、数据安全及性能优化等挑战。
|
4天前
|
算法 安全 Java
【用Java学习数据结构系列】探索Java集合框架的无尽秘密pro
【用Java学习数据结构系列】探索Java集合框架的无尽秘密pro
12 1
|
6天前
|
前端开发 Java API
JAVA Web 服务及底层框架原理
【10月更文挑战第1天】Java Web 服务是基于 Java 编程语言用于开发分布式网络应用程序的一种技术。它通常运行在 Web 服务器上,并通过 HTTP 协议与客户端进行通信。
12 1
|
24天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
1031 11
|
13天前
|
存储 算法 安全
深入理解Java中的集合框架
【9月更文挑战第34天】本文将带你走进Java的集合框架,探索其背后的设计哲学和实现细节。我们将从集合的基本概念出发,逐步深入到具体的接口和类的实现,最后通过一个实际的例子来展示如何在Java程序中高效地使用集合。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和深度理解。
14 1
|
23天前
|
算法 Java
Java项目不使用框架如何实现限流?
Java项目不使用框架如何实现限流?
24 2
|
23天前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
34 2
|
3天前
|
SQL XML 监控
SpringBoot框架日志详解
本文详细介绍了日志系统的重要性及其在不同环境下的配置方法。日志用于记录系统运行时的问题,确保服务的可靠性。文章解释了各种日志级别(如 info、warn、error 等)的作用,并介绍了常用的日志框架如 SLF4J 和 Logback。此外,还说明了如何在 SpringBoot 中配置日志输出路径及日志级别,包括控制台输出与文件输出的具体设置方法。通过这些配置,开发者能够更好地管理和调试应用程序。