关于Log4j 1.x 升级Log4j 2.x 那些事
这篇博文来聊聊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 ?
好了说完了为什么升级,接下来我们聊聊如何升级的问题。
- 首先我们可以看下官方的升级文档: Log4j 1.x 升级到Log4j 2.x 官方参考资料
你可能看的一头雾水,没关系,我们来看点比较实用的
2.1 依赖升级篇
关于这个依赖升级,下面这个图很重要。
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配置和自定义线程日志
- 关于log4j 2.xml 如何配置可以看下我的另一篇博文中的 XML配置篇
关于log4j 2.x 自定义线程日志可以看下我的另一篇博文中的代码中自定义线程日志
0x04 参考博文
本篇完~
喜欢我的博文,欢迎点赞和关注~