关于Log4j 1.x 升级Log4j 2.x 那些事

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/hadues/article/details/101056724

这篇博文来聊聊Log4j 1.x 升级到Log4j 2.x 后发生了改变。

0x01 为什么要将Log4j 1.x升级到Log4j 2.x?

我们知道由于 Log4j 1 代码库存在一些架构缺陷,因此 2015年8月 官方停止维护log4j 1.x

当我们重新打开 Log4j 1.x 官网可以看到如下内容:
在这里插入图片描述

上面的英文大致意思说Log4j 1.x 已经停止维护,建议大家都去升级使用Log4j 2.x吧~

既然官方都这么宣布了,那么我们一起来Log4j 2.x 官网看看吧。

打开Log4j 2.x 官网,我们可以看到
在这里插入图片描述
如果看不懂的,这里大致翻译下:

Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些固有问题。

关于 为什么要升级,我这里做下总结:

  • Log4j 1.x 和Logback 配置文件改动重新加载会导致丢失一些日志记录。
  • log4j2 基于LMAX Disruptor library 比Log4j1.x 和logback 快很多
  • Log4j 1代码库存在一些架构缺陷,因此2015年8月log4j 1.x 官方停止维护

总之,我们知道如下几点就够了

  • log4j 2.x 比Log4j 1.x 和Logback 更好更优秀
  • log4j 2.x 接口和实现分离,log4j-api 模块是log4j2日志接口,log4j-core模块是log4j2日志实现。
  • log4j-api 日志接口比Slf4j API接口功能更强大

log4j 2设计架构图
在这里插入图片描述

0x02 如何把Log4j 1.x 升级到log4j2.x ?

好了说完了为什么升级,接下来我们聊聊如何升级的问题。

你可能看的一头雾水,没关系,我们来看点比较实用的

2.1 依赖升级篇

官方Log4j 1.x 升级Log4j2.x升级参考文档

关于这个依赖升级,下面这个图很重要。

2.1.1 基础的依赖

2.1.1.1 普通项目

添加最基础的Log4j 2日志依赖如下:

   <!--https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api-->
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.12.1</version>
  </dependency>
  <!--https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core-->
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
  </dependency>
  • 这里log4j-api是log4j2日志接口,log4j-core 是log4j2日志实现。
  • 查看最新版本

2.1.1.2 Spring Boot 项目

我们只需要如下添加如下依赖即可:

<!--排除默认的logback日志-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

注意事项

  • log4j-slf4j-impl 不能和log4j-to-slf4j同时使用
    • log4j-slf4j-impl 是SLF4J API 作为日志门面,
    • 然后通过该依赖适配成Log4j 2.x API(日志接口),最后交给Log4j 2.x core(impl 日志实现)
  • log4j-to-slf4j 则是将log4j 2.x API 转换成SLF4J API,最后交给SLF4J 实现类。

2.1.2 统一版本依赖

在项目中显示声明的依赖好处理,对于一些传递的依赖如果Log4j 的 版本不一致可能会警告或者错误提示。

解决方法就是添加如下内容:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-bom</artifactId>
      <version>2.12.1</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

2.1.3 Log4j 1.x API Bridge

如果是老项目,可能有很多代码是使用Log4j 1.x 的API进行的调用,全部更换成新代码无疑是一个头疼的事情。

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.12.1</version>
  </dependency>
</dependencies>
  • 需要移除掉所有的之前的Log4j 1.x 相关依赖
  • 正如下图所示,添加以上依赖后就可以将log4j 1.x API与log4j 2.x API 之间做一个适配,最后交给log4j 2.x Core实现。
  • Log4j Runtime Dependencies
    在这里插入图片描述

2.1.4 Apache Commons Logging Bridge

如果项目之前是实用Commons Logging 作为门面日志,那么官方提供了一个连接桥

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.12.1</version>
  </dependency>
</dependencies>
  • 不需要移除Common Logging 依赖
  • 正如下图所示,添加如上依赖后可以将 Common Logging API 适配成Log4j 2.x API ,然后交给log4j 2.x 实现类实现。
  • Log4j Runtime Dependencies
    在这里插入图片描述

2.1.5 SLF4J Bridge

如果我们不想要实用log4j-api 作为日志门面(日志接口),而是想用SLF4j 那么可以引入这个依赖。

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.12.1</version>
  </dependency>
</dependencies>

注意事项:

  • 不需要移除SLF4J-API 依赖
  • 本质上是 SLF4J 日志门面转Log4j 日志门面
  • 切记,如果使用了这个依赖就不能使用 2.1.6 Log4j to SLF4J Adapter

2.1.6 JUL Adapter

如果之前项目使用的是Java Util Logging门面日志,那么可以加入这个依赖

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jul</artifactId>
    <version>2.12.1</version>
  </dependency>
</dependencies>
  • The Apache Log4j implementation of java.util.logging
  • 正如下图所示,添加如上依赖后可以将 java util logging API 适配成Log4j 2.x API ,然后交给log4j 2.x 实现类实现。
  • Log4j Runtime Dependencies
    在这里插入图片描述

2.1.7 Log4j API to SLF4J API Adapter

如果代码中实用的是Log4j-2.x-API 日志门面,想实用SLF4作为日志门面,那么需要添加如下依赖

  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.12.1</version>
  </dependency>
  • 本质上是Log4j 2.x API日志门面转SLF4j API 日志门面
  • log4j-slf4j-impl 不能和log4j-to-slf4j同时使用,推荐使用log4j-slf4j-impl,否则会丢失一些log4j 2.x API的一些新特性。

2.2 普通web项目依赖配置汇总

		<!--日志系统 -->
		<!--使用Log4j2 start-->
		<!-- log4j 2.x API 日志门面 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!-- log4j 2.x Core 日志实现 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!-- Web模块支持-->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-web</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!--升级兼容包-->
		<!--该依赖会先Log4j 1.x api 转换成log4j 2.x api ,最后使用log4j2.x 实现类 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-1.2-api</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!--该依赖会将Common Logging API 转换成 Log4jx2.x api,最后使用log4j2.x 实现类 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-jcl</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!--将SLF4j 的实现类替换为Log4J 2实现类-->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!-- 将java.util.logging API 通过该依赖转换成log4j2.x api,最后使用log4j2.x 实现类 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-jul</artifactId>
			<version>2.9.1</version>
		</dependency>
		<!-- SLF4J API -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.6.6</version>
		</dependency>
		<!--使用Log4j2 end-->

2.3 Spring Boot 项目

对于Spring Boot 新项目, 我们只需要如下添加如下依赖即可:

<!--排除默认的logback日志-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

2.4 关于web.xml配置

如果是传统的web 项目,需要添加在pom.xml中添加如下依赖

 <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.12.1</version>
  </dependency>

因为org.apache.logging.log4j.web.Log4jServletContextListener需要

web.xml修改如下:

  <!-- 日志配置文件路径-->
  <context-param>
    <param-name>log4jConfiguration</param-name>
    <param-value>classpath:/log4j2.xml</param-value>
  </context-param>

<!--日志配置监听器 -->
  <!-- log4j2 start -->
  <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>
  <!-- log4j2-end -->

注意

  • log4jConfiguration这个单词Log4j 2.x 以后改变了
  • 而且log4j2.xml 最好放在src/main/resources 根目录,否则可能会报找不到错误。

2.5 关于 MDC

我们知道在Log4j 1.x 中,MDC是一种多线程下日志管理实践方式,

关于用法主要用于获取多线程下的一些变量

  • 比如我们可以在代码中定义一个类Log4jServletContextListener 实现ServletContextListener接口
  • 然后获取多线程下的变量,比如上下文,IP地址等。

Log4j 1.x 实用MDC:

class Log4jServletContextListener implements ServletContextListener{
	  @Override
	 public void contextInitialized(ServletContextEvent arg0) {
		// TODO Auto-generated method stub
		super.contextInitialized(arg0);
		InetAddress ia=null;
        try {
        	//获取本地主机信息
            ia=InetAddress.getLocalHost();
            //获取请求的主机名称
            String localname=ia.getHostName();
            //获取请求的IP地址
            String localip=ia.getHostAddress();
            //获取应用程序的上下文
            String path = arg0.getServletContext().getContextPath();

            //格式化输出到日志
			MDC.put("ip", localip);
			MDC.put("path", path.substring(1));
        } catch (Exception e) {
            // TODO Auto-generated catch block
           log.error(e.getMessage(),e);
        }
	}
}

Log4j 2.x 则需要使用ThreadContext

Log4j2 升级后要实用ThreadContext 替换MDC

class Log4j2ServletContextListener implements ServletContextListener{
	  @Override
	 public void contextInitialized(ServletContextEvent arg0) {
		// TODO Auto-generated method stub
		super.contextInitialized(arg0);
		InetAddress ia=null;
        try {
        	//获取本地主机信息
            ia=InetAddress.getLocalHost();
            //获取请求的主机名称
            String localname=ia.getHostName();
            //获取请求的IP地址
            String localip=ia.getHostAddress();
            //获取应用程序的上下文
            String path = arg0.getServletContext().getContextPath();

            //格式化输出到日志
			ThreadContext.put("ip", localip);
			ThreadContext.put("path", path.substring(1));
        } catch (Exception e) {
            // TODO Auto-generated catch block
           log.error(e.getMessage(),e);
        }
	}
}

最后修改日志布局中就可以 用 %X{path} 和%X{ip} 来配置打印该内容

<PatternLayout charset="GB18030" pattern="%d{yyyy/MM/dd HH:mm:ss,SSS} %X{path} %X{ip} %-5level %l %n%msg%n"/>

2.6 Log4j 2.x 与Jboss 兼容性

当前 Jboss 版本 JBoss 6.4 (Jboss EAP 6.x)

15:08:32,192 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-4) JBAS015876: 开始 "******-1.0.war" 的部署(runtime-name: "******-1.0.war"15:08:35,536 WARN  [org.jboss.as.server.deployment] (MSC service thread 1-3) JBAS015852: 无法对 META-INF/versions/9/module-info.class 里的类 /D:/apps/jboss-eap-6.4/bin/content/*******-1.0.war/WEB-INF/lib/log4j-api-2.11.0.jar 进行索引: java.lang.IllegalStateException: Unknown tag! pos=4 poolCount = 32
	at org.jboss.jandex.Indexer.processConstantPool(Indexer.java:665) [jandex-1.2.2.Final-redhat-1.jar:1.2.2.Final-redhat-1]
	at org.jboss.jandex.Indexer.index(Indexer.java:699) [jandex-1.2.2.Final-redhat-1.jar:1.2.2.Final-redhat-1]
	at org.jboss.as.server.deployment.annotation.ResourceRootIndexer.indexResourceRoot(ResourceRootIndexer.java:100) [jboss-as-server-7.5.0.Final-redhat-21.jar:7.5.0.Final-redhat-21]
	at org.jboss.as.server.deployment.annotation.AnnotationIndexProcessor.deploy(AnnotationIndexProcessor.java:51) [jboss-as-server-7.5.0.Final-redhat-21.jar:7.5.0.Final-redhat-21]
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:159) [jboss-as-server-7.5.0.Final-redhat-21.jar:7.5.0.Final-redhat-21]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1980) [jboss-msc-1.1.5.Final-redhat-1.jar:1.1.5.Final-redhat-1]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1913) [jboss-msc-1.1.5.Final-redhat-1.jar:1.1.5.Final-redhat-1]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [rt.jar:1.8.0_144]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [rt.jar:1.8.0_144]
	at java.lang.Thread.run(Thread.java:748) [rt.jar:1.8.0_144]

2.6.1 问题分析

出现这个原因是log4j 2.x 版本太高不兼容 Jboss EAP 6.x.

2.6.2 解决方案

  • Log4j 2对JbossEAP 6.4 的兼容最高版本为2.9.1
  • Spring 5.x + 只支持 JBoss EAP 7.x+ 版本
  • Spring 4.3.29 是支持Jboss EAP 6.x 的最高版本

2.7 Jboss 6.x 不兼容Log4j 2.x 冲突问题

2.7.1 问题描述

经过测试JBoss6.x 不兼容 Log4j 2.x ,默认使用的是Log4j 1.x 的内容,可能会出现xml配置的日志输出不来的问题。

原因:Jboss 会自动在部署的应用上包裹一些日志模块

D:\apps\jboss-eap-6.4\modules\system\layers\base\org\apache\log4j\
D:\apps\jboss-eap-6.4\modules\system\layers\base\org\slf4j

2.7.2 解决方案

在WEB-INF根目录下,创建一个文件jboss-deployment-structure.xml
配置内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--JBoss 6.x 默认采用容器自己的log4j module,该模块是应用log4j 1.x,导致自己配置的log4j 2.x不起作用,因此需要应用做一些设置-->
<jboss-deployment-structure>
    <deployment>
        <!--排除掉Jboss自带的日志模块-->
        <exclusions>
            <module name="org.apache.log4j" />
            <module name="org.jboss.log4j.logmanager" />
            <module name="org.slf4j" />
            <module name="org.slf4j.impl" />
            <module name="org.slf4j.jcl-over-slf4j" />
            <module name="org.slf4j.ext" />
            <module name="ch.qos.cal10n" />
            <module name="org.jboss.logmanager" />
        </exclusions>
    </deployment>
</jboss-deployment-structure>

log4j2.xml 最好 放到 src/main/resources根目录下
jboss-deployment-structure.xml 放到src/main/webapp/WEB-INF/ 根目录下

0x03 log4j 2.x XML配置和自定义线程日志

0x04 参考博文

本篇完~


喜欢我的博文,欢迎点赞和关注~

猜你喜欢

转载自blog.csdn.net/hadues/article/details/101056724