使用 commons-logging 日志实现解耦
解耦需要
日志是实际应用中的一个重要部分,日志系统也有许多开源的实现,如 java.util.logging, logback, log4j系列等。
在使用日志系统时,如果与具体的日志实现耦合太深,如使用 log4j 作为日志的实现,在每一处需要打印日志的地方都会创建日志实例:
logger = LogManager.getLogger("instanceName");
当由于性能或者其他方面的需求需要更换日志实现时,如 log4j 升级到 log4j2,就不得不替换每一处日志实例得创建,将会是一个噩梦般的工作。
因此需要将应用与具体的日志实现解耦。commons-logging 提供了日志实现解耦方案,当然 commons-logging 也提供了简单的日志实现。
commons-logging 的官网地址为:http://commons.apache.org/proper/commons-logging/
其中对 commons-logging 的介绍:
The Logging package is an ultra-thin bridge between different logging implementations. A library that uses the commons-logging API can be used with any logging implementation at runtime. Commons-logging comes with support for a number of popular logging implementations, and writing adapters for others is a reasonably simple task.
commons-logging 简单日志实现
commons-logging 除了提供解耦合方案外,也提供了 org.apache.commons.logging.impl.SimpleLog
作为简单的日志实现。
使用 SimpleLog,需要做相应配置:
- 配置 commons-logging.properties
在 classpath 根路径放置名为 commons-logging.properties
的文件(该路径不可改变),内容为:
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
- 创建 Log 实例
名为配置好了之后就可以创建 Log 实例并直接使用:
private static final Log logger = LogFactory.getLog(CommonsLogTest.class);
输出为,默认日志级别为 INFO。
- 输出配置
当然,SimpleLog 也可以做一些简单的输出配置,打开 SimpleLog 的源码,可以看到在初始化时:
static {
// Add props from the resource simplelog.properties
InputStream in = getResourceAsStream("simplelog.properties");
if(null != in) {
try {
simpleLogProps.load(in);
in.close();
} catch(java.io.IOException e) {
// ignored
}
}
showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
if(showDateTime) {
dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat",
dateTimeFormat);
try {
dateFormatter = new SimpleDateFormat(dateTimeFormat);
} catch(IllegalArgumentException e) {
// If the format pattern is invalid - use the default format
dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
dateFormatter = new SimpleDateFormat(dateTimeFormat);
}
}
}
可以看到最开始试图从 simplelog.properties
读取配置,若不存在后面指定了默认配置,因此只要在 classpath 根路径放置 simplelog.properties
文件即可控制日志输出行为。
可以从 http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html 的关于 SimpleLog
的类介绍中了解配置详情:
Simple implementation of Log that sends all enabled log messages, for all defined loggers, to System.err. The following system properties are supported to configure the behavior of this logger:
org.apache.commons.logging.simplelog.defaultlog
- Default logging detail level for all instances of SimpleLog. Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, defaults to "info".org.apache.commons.logging.simplelog.log.xxxxx
- Logging detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, the default logging detail level is used.org.apache.commons.logging.simplelog.showlogname
- Set to true if you want the Log instance name to be included in output messages. Defaults to false.org.apache.commons.logging.simplelog.showShortLogname
- Set to true if you want the last component of the name to be included in output messages. Defaults to true.org.apache.commons.logging.simplelog.showdatetime
- Set to true if you want the current date and time to be included in output messages. Default is false.org.apache.commons.logging.simplelog.dateTimeFormat
- The date and time format to be used in the output messages. The pattern describing the date and time format is the same that is used injava.text.SimpleDateFormat
. If the format is not specified or is invalid, the default format is used. The default format isyyyy/MM/dd HH:mm:ss:SSS zzz
.
commons-logging 解耦原理
commons-logging 最核心有用的功能是解耦,它的 SimpleLog 实现性能比不上其他实现,如 log4j 等。
首先,日志实例是通过 LogFactory
的 getLog(String)
方法创建的:
LogFatory 是一个抽象类,它负责加载具体的日志实现,分析其 Factory getFactory() 方法:
public static org.apache.commons.logging.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
org.apache.commons.logging.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.
// classpath根目录下寻找commons-logging.properties
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).
// classpath根目录下commons-logging.properties是否配置use_tccl
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;
}
}
}
// 这里真正开始决定使用哪个factory
// 首先,尝试查找vm系统属性org.apache.commons.logging.LogFactory,其是否指定factory
// 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 {
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;
}
// 第二,尝试使用java spi服务发现机制,载META-INF/services下寻找org.apache.commons.logging.LogFactory实现
// 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 {
// META-INF/services/org.apache.commons.logging.LogFactory, SERVICE_ID
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 = 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 {
// 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
}
}
// 第三,尝试从classpath根目录下的commons-logging.properties中查找org.apache.commons.logging.LogFactory属性指定的factory
// 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...");
}
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
// 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.
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;
}
可以看出,抽象类 LogFactory 加载具体实现的步骤如下:
- 从 vm 系统属性 org.apache.commons.logging.LogFactory
- 使用 SPI 服务发现机制,发现 org.apache.commons.logging.LogFactory 的实现
- 查找 classpath 根目录 commons-logging.properties的org.apache.commons.logging.LogFactory 属性是否指定 factory 实现
- 使用默认 factory 实现,org.apache.commons.logging.impl.LogFactoryImpl
LogFactory 的 getLog() 方法返回类型是 org.apache.commons.logging.Log
接口,提供了从 trace 到 fatal 方法。可以确定,如果日志实现提供者只要实现该接口,并且使用继承自 org.apache.commons.logging.LogFactory
的子类创建 Log,必然可以构建一个松耦合的日志系统。
log4j+commons-logging 解耦
使用 commons-logging 解耦 log4j,可按照下列步骤:
- 将 log4j 和 commons-logging 依赖放入 classpath:
<!--commons-logging-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
-
配置 log4j.xml 或者 log4j.properties,放入类路径根目录;
-
使用 commons-logging 得 LogFactory 获取日志实例
log4j 提供了 LogManager.getLogger(String)
方法创建日志实例,但是为了日志实现的解耦,必须使用 commons-logging 提供的抽象工厂 LogFactory 创建日志实例:
Log logger = LogFactory.getLog(String)
接下来就可以使用 logger 输出日志信息了。
log4j 如何被 commons-logging 加载?
根据上述 commons-logging 解耦的原理,如果 log4j 是通过继承抽象工厂 org.apache.commons.logging.LogFactory 来创建实现 org.apache.commons.logging.Log 接口的日志实例的,那么就可以通过 3 种方法让 commons-logging 加载 log4j,分别是:
- 从 vm 系统属性 org.apache.commons.logging.LogFactory
- 使用 SPI 服务发现机制,发现 org.apache.commons.logging.LogFactory 的实现
- 查找 classpath 根目录 commons-logging.properties 的 org.apache.commons.logging.LogFactory 属性是否指定 factory 实现
但是,查看 log4j 的 jar 包,发现在 META-INF 下面并没有名为 services 得文件夹,更别说 org.apache.commons.logging.LogFactory 文件了。而且,Logger 类也没有实现 commons-logging 提供的 Log 接口。log4j 中也没有找到继承 org.apache.commons.logging.LogFactory 的类。
那么,log4j 是如何被 commons-logging 加载的呢?
按照上述 LogFactory 寻找 factory 实现的流程,LogFactory 在找不到 org.apache.commons.logging.LogFactory 实现时,会使用默认实现 org.apache.commons.logging.impl.LogFactoryImpl。
通过分析 LogFactoryImpl 的 getInstance()
方法,其调用以下方法获得 logger 实例:
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);
}
}
discoverLogImplementation
方法如下:
- 该方法首先查找用户是否指定了日志实现
- classesToDiscover 定义如下:
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 数组元素的顺序,依次创建对应 Log 实例,直到返回成功创建的 Log 实例:
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;
}
// No user specified log; try to discover what's on the classpath
//
// Note that we deliberately loop here over classesToDiscover and
// expect method createLogFromClass to loop over the possible source
// classloaders. The effect is:
// for each discoverable log adapter
// for each possible classloader
// see if it works
//
// It appears reasonable at first glance to do the opposite:
// for each possible classloader
// for each discoverable log adapter
// see if it works
//
// The latter certainly has advantages for user-installable logging
// libraries such as log4j; in a webapp for example this code should
// first check whether the user has provided any of the possible
// logging libraries before looking in the parent classloader.
// Unfortunately, however, Jdk14Logger will always work in jvm>=1.4,
// and SimpleLog will always work in any JVM. So the loop would never
// ever look for logging libraries in the parent classpath. Yet many
// users would expect that putting log4j there would cause it to be
// detected (and this is the historical JCL behaviour). So we go with
// the first approach. A user that has bundled a specific logging lib
// in a webapp should use a commons-logging.properties file or a
// service file in META-INF to force use of that logging lib anyway,
// rather than relying on discovery.
if (isDiagnosticsEnabled()) {
logDiagnostic(
"No user-specified Log implementation; performing discovery" +
" using the standard supported logging implementations...");
}
// 用户没有指定日志实现的情况下,按照classesToDiscover数组元素的顺序,依次创建对应Log实例,直到返回成功创建的Log实例
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
方法如下:
- 该方法首先试图获取 commons-logging.properties 中的 org.apache.commons.logging.Log 指定的日志实现
- 试图获取 commons-logging.properties 中的 org.apache.commons.logging.log 指定的日志实现
- 试图获取 vm 系统属性 org.apache.commons.logging.Log 指定的日志实现
- 试图获取 vm 系统属性 org.apache.commons.logging.log 指定的日志实现
- 都没有返回 null
private String findUserSpecifiedLogClassName() {
if (isDiagnosticsEnabled()) {
logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'");
}
// 试图获取commons-logging.properties 中的org.apache.commons.logging.Log指定的日志实现
String specifiedClass = (String) getAttribute(LOG_PROPERTY);
if (specifiedClass == null) { // @deprecated
if (isDiagnosticsEnabled()) {
logDiagnostic("Trying to get log class from attribute '" +
LOG_PROPERTY_OLD + "'");
}
// 试图获取commons-logging.properties 中的org.apache.commons.logging.log指定的日志实现
specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD);
}
if (specifiedClass == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("Trying to get log class from system property '" +
LOG_PROPERTY + "'");
}
try {
// 试图获取vm系统属性org.apache.commons.logging.Log指定的日志实现
specifiedClass = getSystemProperty(LOG_PROPERTY, null);
} catch (SecurityException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("No access allowed to system property '" +
LOG_PROPERTY + "' - " + e.getMessage());
}
}
}
if (specifiedClass == null) { // @deprecated
if (isDiagnosticsEnabled()) {
logDiagnostic("Trying to get log class from system property '" +
LOG_PROPERTY_OLD + "'");
}
try {
// 试图获取vm系统属性org.apache.commons.logging.log指定的日志实现
specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null);
} catch (SecurityException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("No access allowed to system property '" +
LOG_PROPERTY_OLD + "' - " + e.getMessage());
}
}
}
// Remove any whitespace; it's never valid in a classname so its
// presence just means a user mistake. As we know what they meant,
// we may as well strip the spaces.
if (specifiedClass != null) {
specifiedClass = specifiedClass.trim();
}
return specifiedClass;
}
如此一来,如果像上述一样没有指定任何 org.apache.commons.logging.Log
的实现,那么 commons-logging 首先使用的是 org.apache.commons.logging.impl.Log4JLogger
作为 Log 实现,可以看到 Log4jLogger 是实现了 Log 接口的:
public class Log4JLogger implements Log, Serializable
Log4jLogger 则是通过组合 log4j 提供的 org.apache.log4j.Logger
,来提供 trace 到 fatal 功能。
在 log4j 的 jar 包没有导入到 classpath 之前,这个类是无法通过编译的。
到现在应该知道了 commons-logging 是如何与 log4j 实现解耦的了,不是通过 commons-logging 提供的抽象工厂,而是通过 org.apache.commons.logging.impl.LogFactoryImpl
加载具体的日志实现。不得不说,在我看来这种设计极其不好,完全可以用抽象工厂提供的日志实现加载!
log4j2+commons-logging 解耦
log4j2 是 Apache 推出的 log4j 的升级版本,在性能等各方面都做了提升。
要想使用 commons-logging 解耦 log4j2,可按照下列步骤:
- 导入log4j2, commons-logging, log4j-jcl 依赖
其中,log4j-jcl 是用来作为 log4j 和 commons-logging 的桥接的。
<!--log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency>
- 配置好 log4j2 依赖的配置
log4j2 支持了更多的配置方式,包括 xml 和 json 格式;若不指定配置文件位置,将其放置在 classpath 根路径。
- 创建日志实例使用
log4j2 也提供了自己的日志实例创建的方法:
logger = LogManager.getLogger(Log4jTwoTest.class);
为了实现解耦,需要使用 commons-logging 提供的抽象工厂 LogFactory.getLog(String)
方法获取日志实例即可;
解耦原理:log4j-jcl 提供了解耦的实现,观察其 jar 包:
该文件内容为:org.apache.logging.log4j.jcl.LogFactoryImpl
根据 commons-logging 抽象日志工厂的第二步骤 spi 服务发现规则,抽象工厂使用 org.apache.logging.log4j.jcl.LogFactoryImpl
作为实际产生日志实现的工厂。
分析 LogFactoryImpl 的代码:
private final LoggerAdapter<Log> adapter = new LogAdapter();
private final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>();
@Override
public Log getInstance(final String name) throws LogConfigurationException {
return adapter.getLogger(name);
}
Log
实例是由 LogAdapter
类的 getLogger(String)
方法创建的,其继承了抽象适配器 AbstractLoggerAdapter<Log>
的 getLogger 方法:
public L getLogger(String name) {
LoggerContext context = this.getContext();
ConcurrentMap loggers = this.getLoggersInContext(context);
Object logger = loggers.get(name);
if(logger != null) {
return logger;
} else {
loggers.putIfAbsent(name, this.newLogger(name, context));
return loggers.get(name);
}
}
最终调用 LogAdapter 的 newLogger()
方法产生日志实例:
@Override
protected Log newLogger(final String name, final LoggerContext context) {
return new Log4jLog(context.getLogger(name));
}
Log4jLog 组合了 log4j2 中的 ExtendedLogger
提供日志打印功能。
可以看出,log4j2 和 commons-logging 的解耦,完全使用了 commons-logging 提供的 spi 服务发现机制。
参考: