Log4j2 source code analysis series :( a) configuration is loaded

Foreword

In the actual development project, the log is always not open around the topic. This series of articles attempts to slf4j and log4j2日志体系为例, from the point of view of the source log works.

Learning logging framework, we must first be familiar with all types of logging framework, here we recommend two articles, will not repeat them.

https://www.cnblogs.com/rjzheng/p/10042911.html

https://www.cnblogs.com/chanshuyi/p/something_about_java_log_framework.html

For log4j2, the configuration file has several categories: properties, xml, json / jsn and yaml / yml, usually we use xml majority.

Under normal circumstances, we will create / resources file log4j2.xml into the project folder. Most rely on the use maven project management environment configuration may also be divided in different environments different log4j2 read files, then it is generally in / profiles / $ {env} / folder.

Most people should be "learn" other items, to copy the configuration over again tinkering. However, if you thought about:

  1. Why write this profile? Do not write, then what would be the problem?
  2. Name the profile of what rules? Why do we usually see is the log4j2.xml, rather than the other names?
  3. This configuration file is how loaded?

Answer these questions, it is the original intention of this article.

prompt

1. This method will be used to debug to configure log4j2 loading process as the main line, describe their work processes; little effect offshoot details will be ignored, interested readers can access to the source code itself.

Figure 2. Multi alert! Check with your computer better.

3. Try hands-on, in order to deepen understanding.

 

Preparing the Environment

Before reading the source code, make sure that the introduction of slf4j and log4j2 dependencies, as well as the adaptation package. With maven example, this paper introduces the sample program:

        <!-- slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <!-- bridge --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.7</version> </dependency> <!-- log4j2 --> <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-api</artifactId> <version>2.7</version> </dependency>

Source

First, we create a new java file, break point to start debugging.

Enter getLogger method. It can be seen, for specific Logger factory in LoggerFactory.

Enter getILoggerFactory method.

Here's a bunch of first logic do not control, we will eventually enter the 418 line.

接下来进入真正的日志绑定环节。由于我们只引入了log4j2,这里会直接找到它,继而绑定。StaticLoggerBinder就在log4j2的包中。

程序走到61行,可以看到这里使用饿汉方式实现了单例。41行实例化StaticLoggerBinder,会跳到53行,我们进去看看细节。

可以看出Log4jLoggerFactory继承了AbstractLoggerAdapter这个抽象的日志适配器。这个抽象适配器中定义了若干方法,别急,马上会提及。

回到LoggerFactory,通过方法getLoggerFactory,我们会得到刚刚创建出来的Log4jLoggerFactory:

接下来,我们进入log4j2的getLogger环节。

可以看到getLogger是个接口方法,并且有3个实现。

还记得我们刚才获取到的Log4jLoggerFactory吗?AbstractLoggerAdapter是它的父类,由此我们会走到AbstractLoggerAdapter的getLogger中。

getContext是AbstractLoggerAdapter的抽象方法,因此,我们下一步会走到Log4jLoggerFactory的getContext方法中。

这里用反射定位到我们的日志(anchor中文译为"锚",可以理解为类似文件句柄一类的东西),这里得出的anchor为"",因此会进入后面的语句。

最终,我们会拿到一个AppClassLoader,LogManager会利用这个类加载器获取上下文。

进入getContext看看:

请注意:这里的factory是Log4jContextFactory,它是在LogManager中的静态代码块中初始化的,具体细节后面会补充。

现在,我们先进入getContext看看:

这里的getContext是ContextSelector接口中的方法,下一步会进入ClassLoaderContextSelector中的getContext中。至于slector是怎么初始化的,我们放在后面一起说。

下面几步都是上下文相关操作,不再贴出,最终会回到这里:

然后走到152行的ctx.start方法,进去看下:

到现在,终于要开始加载配置了!!!

接下来几步比较直观,贴图示意:

在这里,先创建ConfigurationFactory的实例,然后获取配置。至于ConfigurationFactory的实例创建,这里不再说明,可自行查看。

接下来,进入getConfiguration方法:

进入该方法:

请注意,这里的getFactories已经很明显地告诉我们,这里有4个工厂(均继承自ConfigurationFactory ),分别处理前文提到的四类配置文件类型:properties、xml、json/jsn以及yaml/yml。调用factory.getSupportedTypes()方法即可获取到各类后缀。以xml为例:

其他类型文件同理。

好了,回到加载配置的方法,可以看到426行代码判断是否支持所有文件类型。其实最终的核心代码是453~467行:

这里尝试用不同的条件获取config,如果最终config为null,就会打印error日志,告诉你没有找到配置文件。由于目前我们还没有配置,就会走到466行。

 

现在,你可以在/resources路径下增加一个log4j2文件,填写一下简单配置,就会在459行得到config了。我们来看看getConfiguration的细节:

 

 可以看出,这里就是按照各种条件拼接处配置文件的名字。

 以最常见的log4j2.xml为例:

上图中,我们已经得到了配置文件的名字:log4j2.xml。

同时可以看到,prefix为log4j2,suffix为文件后缀。

其中prefix(505行)是写死在ConfigurationFactory中的:

所以,我们配置时定义的文件名,需要遵循规范,而不能随意命名。 

现在有了配置文件名,就可以加载了:

进入方法内部: 

现在,url已经获取到了。它的值是"项目路径/target/classes/log4j2.xml"。

后面的事情就是从文件加载内容( 517行,涉及到类加载器的知识,请自行查看)。

再然后,就是读取xml文件的内容啦:

走到这里,就开始读取xml文件了。这部分内容且待下回分解。 

 

遗留问题:LoggerManager的factory及其内部的selector是怎么初始化的?

其实,在调用LogManager.getContext(cl, false);之前,LoggerManager中的静态代码块会提前被调用,我们看一下:

 

我们看89~100行代码即可:

进入方法ProviderUtil.getProviders()内部查看:

可以看到provider是使用懒汉方式实现的单例(你会发现89行代码中ProviderUtil.hasProviders()方法执行时已经创建过了,因此这里直接返回。注意创建过程有个细节,后面要用到),用于确定各个factory的优先级。

我们重点看91行代码内部细节:

在96行,加载class,98行又将其转换为LoggerContextFactory的子类(也就是Log4jContextFactory)。

那么问题来了,className是啥,为啥它指定了Log4jContextFactory?

其实,在前面创建Provider实例时,构造器中会读取log4j-core中的配置文件,其中就包含className对应的属性:

就这样,得到了className:org.apache.logging.log4j.core.impl.Log4jContextFactory。

接着往下走:

可以看到这里会用反射的方式实例化Log4jContextFactory对象,会调用Log4jContextFactory的无参构造器:

createContextSelector方法,就会初始化selector啦:

后续的初始化细节就不再展开啦。

最后会走到这里:

至此,factory创建完毕。

 

现在,你应该可以回答文首的三个问题了吧?

总结

本文通过调试,描述了log4j2日志配置加载的主线(忽略了很多细节,比如可以配置path等等),后续的文章将会进一步描述配置文件的解析过程。 

希望读者通过本文,能够对log4j2的配置加载过程有更为深入的理解。

 

最后,作者水平有限,难免错漏,欢迎指正及交流,共同进步。

Guess you like

Origin www.cnblogs.com/xiaoxi666/p/11426259.html