由于log4j1.x采用同步的方式打印log,当项目中打印log的地方很多的时候,频繁的加锁拆锁会导致性能的明显下降。log4j推出了异步logging的方式,所以项目准备升级log4j2.x。
备注:项目使用maven管理第三方类库,所以jar包的替换是通过更改maven配置的方式进行更改的。
官方文档链接:https://logging.apache.org/log4j/2.x/manual/migration.html
1、升级jar包
需要将原来的log4j的jar包替换成log4j2.x的相关jar包。在maven的pom.xml文件中修改依赖:
原依赖(需要删除):
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
新依赖:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>「版本号,例如:2.11.1」</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>「版本号,例如:2.11.1」</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>「版本号,例如:2.11.1」</version>
</dependency>
注意:log4j-api和log4j-core是必须的核心包,log4j-1.2-api是官方推出的平稳的过度包。
Perhaps the simplest way to convert to using Log4j 2 is to replace the log4j 1.x jar file with Log4j 2's log4j-1.2-api.jar. However, to use this successfully applications must meet the following requirements:
- They must not access methods and classes internal to the Log4j 1.x implementation such as Appenders, LoggerRepository or Category's callAppenders method.
- They must not programmatically configure Log4j.
- They must not configure by calling the classes DOMConfigurator or PropertyConfigurator.
2、升级api
关键的API需要切换的有:
1)包名:org.apache.log4j替换为org.apache.logging.log4j
2)Logger实例的获取方式:org.apache.log4j.Logger.getLogger() 替换为org.apache.logging.log4j.LogManager.getLogger();
3)MDC/NDC:替换成ThreadContext;
4)Logger等级Level的set和get:参照如下官方文档的描述进行替换
5)LogManager.shutdown():新版本中增加了自动关闭的功能,也可以参照如下官方文档第6条进行手动关闭。
官方文档:
For the most part, converting from the Log4j 1.x API to Log4j 2 should be fairly simple. Many of the log statements will require no modification. However, where necessary the following changes must be made.
- The main package in version 1 is org.apache.log4j, in version 2 it is org.apache.logging.log4j
- Calls to org.apache.log4j.Logger.getLogger() must be modified to org.apache.logging.log4j.LogManager.getLogger().
- Calls to org.apache.log4j.Logger.getRootLogger() or org.apache.log4j.LogManager.getRootLogger() must be replaced withorg.apache.logging.log4j.LogManager.getRootLogger().
- Calls to org.apache.log4j.Logger.getLogger that accept a LoggerFactory must remove the org.apache.log4j.spi.LoggerFactory and use one of Log4j 2's other extension mechanisms.
- Replace calls to org.apache.log4j.Logger.getEffectiveLevel() with org.apache.logging.log4j.Logger.getLevel().
- Remove calls to org.apache.log4j.LogManager.shutdown(), they are not needed in version 2 because the Log4j Core now automatically adds a JVM shutdown hook on start up to perform any Core clean ups.
- Starting in Log4j 2.1, you can specify a custom ShutdownCallbackRegistry to override the default JVM shutdown hook strategy.
- Starting in Log4j 2.6, you can now use org.apache.logging.log4j.LogManager.shutdown() to initiate shutdown manually.
- Calls to org.apache.log4j.Logger.setLevel() or similar methods are not supported in the API. Applications should remove these. Equivalent functionality is provided in the Log4j 2 implementation classes, see org.apache.logging.log4j.core.config.Configurator.setLevel(), but may leave the application susceptible to changes in Log4j 2 internals.
- Where appropriate, applications should convert to use parameterized messages instead of String concatenation.
- org.apache.log4j.MDC and org.apache.log4j.NDC have been replaced by the Thread Context.
3、升级配置文件
Log4j2中,在版本2.4及以后开始支持.properties格式的配置文件,但是在实际使用中感觉2.4和2.4.1版本配置.properties后没有实际上生效,更换2.6版本后该问题解决。如下提供了简单的配置文件升级对应的示例。
简单配置升级示例:
log4j 1.x版本配置文件:
log4j.rootLogger=info, R, stdout
log4j.logger.org.apache.hadoop=OFF
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n - %L
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=logs/context.log
log4j.appender.R.MaxFileSize=20000KB
log4j.appender.R.MaxBackupIndex=3
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d [%t] %-5p %c - %m%n - %L
升级后对应的配置文件:
status=INFO
name=PropertiesConfig
rootLogger.level=INFO
rootLogger.appenderRef.R.ref=R
rootLogger.appenderRef.stdout.ref=stdout
appender.R.type=RollingFile
appender.R.name=R
appender.R.fileName=logs/context.log
appender.R.filePattern=logs/context.log-%d{dd}-%i.log
appender.R.layout.type=PatternLayout
appender.R.layout.pattern=%d [%t] %-5p %c - %m%n - %L
appender.R.strategy.type=DefaultRolloverStrategy
appender.R.strategy.max=3
appender.R.policies.type=policies
appender.R.policies.size.type=SizeBasedTriggeringPolicy
appender.R.policies.size.size=20000KB
appender.stdout.type=Console
appender.stdout.name=stdout
appender.stdout.layout.type=PatternLayout
appender.stdout.layout.pattern=%d [%t] %-5p %c - %m%n - %L
logger.hadoop.name=org.apache.hadoop
logger.hadoop.level=OFF
4、升级加载指定配置文件的方式
在log4j1.x的版本中,可以通过DOMConfigurator或PropertyConfigurator来加载制定的配置文件,但是升级2.x后该API不可用。可以通过Configurator的静态方法initializ来初始化log4j2。
代码示例:
URL url = Main.class.getClassLoader().getResource("config/log4j.properties");
ConfigurationSource source = null;
try {
source = new ConfigurationSource(url.openStream(), url);
Configurator.initialize(null, source);
} catch (IOException e) {
e.printStackTrace();
}
备注:ConfigurationSource有很多个构造器,可以在不同情况下使用不同的构造器来加载配置文件。
5、开启异步日志
Log4j2.x的异步日志有两种实现方式:全局异步方式和混合异步方式。其中,全局异步方式实现起来操作更简单、性能提升的更明显。此处以全局异步的配置为例。
1)添加依赖
首先,需要给项目添加disruptor的jar包,log4j2.9以及2.9以上,需要添加disruptor-3.3.4.jar或以上版本;低于log4j2.9的情况下,添加 disruptor-3.0.0.jar 及以上版本即可。
参考官方文档:Log4j-2.9 and higher require disruptor-3.3.4.jar or higher on the classpath. Prior to Log4j-2.9, disruptor-3.0.0.jar or higher was required.
maven项目的添加方式:在模块的pom.xml文件中添加disruptor的依赖,log4j 2.9及以上版本要求disruptor的版本在3.3.4以上。maven的官方库链接:https://mvnrepository.com/artifact/com.lmax/disruptor/3.3.4
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
2)配置系统变量
然后,只需要配置系统变量Log4jContextSelector的值为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector,即可开启全局异步日志打印功能。
参考官方文档:To make all loggers asynchronous, add the disruptor jar to the classpath and set the system property log4j2.contextSelector to org.apache.logging.log4j.core.async.AsyncLoggerContextSelector.
配置系统变量Log4jContextSelector有两种方式:
方式一:log4j2会自动在resource下加载log4j2.component.properties文件,使用可以在resources路径下创建文件并命名为log4j2.component.properties,添加如下内容,配置该系统变量:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
方式二:配置JVM的启动参数,在启动命令中添加如下参数:
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
上述参数可以在命令行执行java命令时添加,也可以在使用ide运行的情况下,在启动配置中进行配置。
6、踩坑记录
1)版本问题:2.4以后开始支持.properties配置文件,但是实践发现2.4和2.4.1使用.properties文件后,会去寻找该文件,但是配置的信息是有问题的,出现了无法打印log的问题,升级版本到2.6之后问题解决;
2)外部引用的库调用log4j1.x的问题:无法解决,引入了过度包,让它们继续使用1.x,此时使用log4j1.x的外部库是无法使用log4j2的配置的。
3)配置文件的配置中,log4g1.x中有些size based RollingFile没有配置filePattern,但是升级后如果配置filePattern就会报错。
4)在运行jar包的情况下,如果需要加载指定的配置文件,这时如果选用的ConfigurationSource的构造器的参数包含File,并且该File文件位于jar包内部,那么log4j2的初始化就有可能会失败,因为java无法通过new File()的方式去访问jar包内部的文件,这是由于java会把jar文件视为一个文件,而不是一个目录。这时可以通过ClassLoader的getResource方法去获取该文件的URL,然后通过url.openStream打开输入流,使用含InputStream参数的ConfigurationSource构造器。详情可以参考第4步的实例。