Log4j升级Log4j2中遇到的一些问题
最近项目为了对接docker,升级日志组件,但是由于项目用到的一些东西版本比较低,所以遇到了一些问题。
项目版本说明:
- Spring版本 3.2.9
- JDK版本 1.7
- Servlet版本 2.5
日志改造说明
1. maven依赖
<!--log4j2日志配置-->
<!--桥接使用log4j的项目-->
<dependency>
<artifactId>log4j-1.2-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
<version>2.11.1</version>
</dependency>
<!-- 使用slf4j作为统一日志接口-->
<dependency>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
<version>1.7.25</version>
</dependency>
<!-- log4j2依赖-->
<dependency>
<artifactId>log4j-core</artifactId>
<groupId>org.apache.logging.log4j</groupId>
<version>2.11.1</version>
</dependency>
<!-- 桥接使用common-logging的项目-->
<dependency>
<artifactId>log4j-jcl</artifactId>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
<groupId>org.apache.logging.log4j</groupId>
<version>2.11.1</version>
</dependency>
<!-- 让slf4j选择log4j2作为日志组件实现-->
<dependency>
<artifactId>log4j-slf4j-impl</artifactId>
<groupId>org.apache.logging.log4j</groupId>
<version>2.11.1</version>
</dependency>
<!-- web项目依赖-->
<dependency>
<artifactId>log4j-web</artifactId>
<groupId>org.apache.logging.log4j</groupId>
<version>2.11.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<artifactId>disruptor</artifactId>
<groupId>com.lmax</groupId>
<version>3.4.2</version>
</dependency>
关于slf4j版本的说明,因为使用的log4j2版本较低,因为“1.8”大版的slf4j修改了logger的绑定机制,需要日志框架做修改来适配“1.8”大版的slf4j,但是目前“2.11.1”的log4j也还没有做适配。使用的各种桥接包都是为了统一日志组件,其实应用的原理无非是SPI。(一定要排除所有的log4j的jar包)
2. web.xml 改造
<!-- 日志记录 -->
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>classpath:META-INF/log/log4j2.xml</param-value>
</context-param>
<!-- log4j2-begin -->
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
这里出现的问题比较多,我一一说明,
- servlet版本
并非maven中依赖的servlet的版本就是servlet的版本,要以web.xml中xsd为准,采用servlet3.0以及以上的版本是不需要进行上述配置的,采用官方文档的描述,
Log4j 2 "just works" in Servlet 3.0 and newer web applications. It is capable of automatically starting when the application deploys and shutting down when the application undeploys. Thanks to the [ServletContainerInitializer] API added to Servlet 3.0, the relevantServletContextListener classes can be registered dynamically on web application startup.
当采用servlet2.5的时候,一定要按照上面的配置进行,详细的配置请参考官方文档log4j2web配置。关于日志的配置这里不做叙述,请参考官方文档。
问题
tomcat服务启动的时候打印出了一段错误日志如下,
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
上面可以看到其实在web.xml中是配置了log4jConfiguration的,所以日志配置文件是一定存在的,在确定命名以及路径都没有问题之后。我特意找了log4j2的源码进行查看,这段错误日志是在org.apache.logging.log4j.core.config.ConfigurationFactory.Factory#getConfiguration(org.apache.logging.log4j.core.LoggerContext, java.lang.String, java.net.URI)中输出的,这说明日志组件初始化的过程一定出现了问题,仔细查看web.xml中配置的所有内容,能够引发这个问题的只有listener,一共配置了两个listener,一个是spring容器,一个便是日志组件,而Spring容器是先于日志容器初始化的,于是在两个监听器的contextInitialized()方法中打了断点,发现果然在spring容器初始化的初始化了日志组件,spring内部使用的common-logging组件,由于使用了jcl进行桥接,所以会调用log4j2组件进行初始化,问题就出在这里,我们看下图,
spring初始化调用顺序
log4j2获取配置 由于先初始化了spring容器,spring容器内部调用了日志初始化,所以我们在web.xml中配置的contextConfigLocation便不起作用,(有兴趣的可以看一下这段代码)导致log4j2找不到配置文件,最终打印了错误日志。
在spring容器初始化之后,按照我们配置的listener的顺序,轮到了日志组件的初始化,如下图, log4j2初始化 log4j2在检查到日志context已经初始化后之后会进行重新配置context,这个时候我们配置的contextConfigLocation才重新起到了作用,关于详细的过程,大家可以查看源码。
在这之后,日志回复正常输出,虽然对程序的影响不大,但是在logcontext自身初始化之前,打印的日志是不正常的,放一张图给大家看
image.png
!所以一定要按照官方说明,先初始化日志组件,servlet3.0中,日志组件是自动初始化的,在Log4jServletContainerInitializer中可以看到这样一段注释,In a Servlet 3.0 or newer environment, this initializer is responsible for starting up Log4j logging before anything else happens in application initialization. For consistency across all containers, if the effective Servlet major version of the application is less than 3.0, this initializer does nothing,这也说明了日志初始化的顺序