Log4j2分析与实践-源码分析(一)

    在学会使用Log4j2之后,有必要继续研究其实现原理吗?这还真不好说,个人推荐尽可能深入地去理解它,因为当你在项目中,遇到诸如日志没有正常打印时,如果你对Log4j2有足够深入的理解,那么可能你会花更少的时间来排查问题。除此之外,深入地研究任何一款开源项目,学到的不仅仅是这个项目本身,还会学到很多设计思想,可以利用到日常工作中。

    没有下载Log4j2源代码的读者可以去这里下载:Log4j2源码

    通过之前的系列文章读者可以知道,在需要打印日志的地方,使用org.apache.logging.log4j.LogManager来获取一个Logger。比如需要在A类中打印日志,那么就需要在A类中通过LogManager.getLogger来获取Logger,若Logger是在A类中定义的一个静态常量:


private static final Logger LOGGER = LogManager.getLogger("loggerName");


    那么通过JVM加载机制可知,当A被加载并初始化时,LogManager也会被加载并初始化。本篇文章就会讲解LogManager的初始化过程。

    当LogManager被初始化时,static{}块中的代码会执行,它的作用只有一个:初始化LoggerContextFactory。我们看看static块中这段代码:


/** 可以通过设置log4j2.loggerContextFactory属性(即代码中的FACTORY_PROPERTY_NAME变量)来指定一个LoggerContextFactory实现。
 * 会在classpath下查找log4j2.component.properties文件并解析配置的属性。 */
final PropertiesUtil managerProps = PropertiesUtil.getProperties();
/** 从属性配置中查找指定的LoggerContextFactory全路径名称,即log4j2.loggerContextFactory。 */
final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
if (factoryClassName != null) {
	try {
		/** 根据factoryClassName实例化一个LoggerContextFactory。这里需要注意,如果在系统属性中配置
		 * 了log4j.ignoreTCL=true(即不使用线程上下文加载器),那么Log4j2会使用默认的加载器
		 * (即使用Class.forName方式)来加载指定的Factory。若设置了log4j.ignoreTCL=true(即忽略线程上下文加载器),
		 * 就会使用默认加载器,否则使用线程上下文加载器。后续的实例化中也会遇到类似的逻辑, 那么这个逻辑会对我们造成
		 * 怎样的影响呢?如果同一个应用中,LogManager被不同的加载器先后加载后,会导致前一次实例化的LoggerContextFactory
		 * 会被后一次实例化的覆盖。也就会遇到
		 * <a href="https://www.sanesee.com/article/log4j2-async-logger-context-selector-cast-error" target="_blank">
		 *  Log4j2异步Logger转换失败排查记录</a>问题。 */
		factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
	} catch (final ClassNotFoundException cnfe) {
		LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
	} catch (final Exception ex) {
		LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
	}
} 

if (factory == null) {
final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
/**
 * 在OSGI环境中,下面对ProviderUtil的调用会阻塞,直到Provider被准备好。
 */
if (ProviderUtil.hasProviders()) {
	/** 当用户没有指定LoggerContextFactory时,就会查找classpath下的所有META-INF/log4j-provider.properties文件,
	 * 并使用其配置实例化Factory。log4j-provider.properties中定义了需要使用的Factory信息, 比如
	 * log4j-core/src/main/resources/META-INF/log4j-provider.properties的内容如下:
	 * LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory
	 * Log4jAPIVersion = 2.6.0 FactoryPriority= 10
	 * 它指定的Factory为org.apache.logging.log4j.core.impl.Log4jContextFactory,api的版本为2.6.0,优先级为10。
	 * 数字越大,优先级越高。 */
	for (final Provider provider : ProviderUtil.getProviders()) {
		/** 加载log4j-provider.properties指定的Factory。 */
		final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
		if (factoryClass != null) {
			try {
				/** 将加载的Factory放到优先级Map factories中。 */
				factories.put(provider.getPriority(), factoryClass.newInstance());
			} catch (final Exception e) {
				LOGGER.error("Unable to create class {} specified in {}", factoryClass.getName(),
						provider.getUrl().toString(), e);
			}
		}
	} if (factories.isEmpty()) {
		LOGGER.error("Log4j2 could not find a logging implementation. " +
				"Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
		/** 当没有找到Factory时(通常是由于log4j-core.jar没有加到classpath中),
		 * 使用默认的Factory SimpleLoggerContextFactory。 */
		factory = new SimpleLoggerContextFactory();
	} else if (factories.size() == 1) {
		/** 当只有一个Factory时,直接使用这个Factory。 */
		factory = factories.get(factories.lastKey());
	} else {
		final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
		for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
			sb.append("Factory: ").append(entry.getValue().getClass().getName());
			sb.append(", Weighting: ").append(entry.getKey()).append('\n');
		}
		/** 当找到的Factory超过1个时,使用优先级最高的Factory。 */
		factory = factories.get(factories.lastKey());
		sb.append("Using factory: ").append(factory.getClass().getName());
		LOGGER.warn(sb.toString());
	}
} else {
	LOGGER.error("Log4j2 could not find a logging implementation. " +
			"Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
	factory = new SimpleLoggerContextFactory();
}
}