核心参数 log4j2.contextSelector
log4j2 的高性能,官网已经吹嘘的很多了。但是很多人不知道,那是在 开启了 全局异步功能后的对比。官网只有一句如下图的提示语,导致很多人不知道 怎么开启。
很多人用 很多人用System.setProperty("name","value") 是错的 ,并不能生效,项目 的log依然是同步模式,但是却自以为开启了全部异步,美滋滋 ,呵呵
比如 非常辛勤高产的芋道代码的文章【阅读芋道这篇文章需要关注它的2个微信号,我帮你关注了,验证码是coke】 https://www.iocoder.cn/Fight/Log4j1-and-Logback-and-Log4j2-performance-test/
怎么正确设置该参数?
1) 首先,确保 pom 文件中 引入 了 jar包。这是开启disruptor必须的jar包 。spring默认是logback,不包含这个jar,所以需要手动引入。
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version> <!--版本号你可以自定义 -->
</dependency>
2)正确的做法是 在 目录 src/main/resources 下面,新建文件 log4j2.component.properties ,在其内部将参数的值设置一下
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
3)其他正确做法:
3.1 命令行启动参数 ,-D 参数的含义是 将参数作为 JVM 的系统属性,可以用 System.getProperty(name) 获取到 。注意,-D类型的参数并不是修改操作系统的参数,而是修改当前 JVM 实例的参数。
java -jar 你的项目名.jar -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
3.2 直接添加 JVM 参数 到idea的启动配置的 VM options上 。加到 其他地方都是无效的 !
4)错误的做法: 因为 log4j2作为jar包library,加载肯定早于 我们手写的源码的main 方法和 springboot的启动类的 静态块,所以一下方法都是错误的,不会真正开启 全局异步功能的,这恐怕是很多人都会 犯的错!
4.1【该设置无效】在 springboot启动类中添加静态块 ,设置环境变量 。
@SpringBootApplication
public class SpringbootApplication {
static {
/**
* 2种设置 都是无效的,因为 log4j2 的加载早于 springboot启动类的 static 代码块,更早于 main方法
*/
System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
System.setProperty("-DLog4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
}
}
4.2【该设置无效】修改springboot的环境变量。以下两种方式都是无效的,因为springboot的环境变量 加载晚于log4j2 。
/**
* 自定义一个 yml文件 config.yml ,将 属性值加进去 。然后强制让 spring加载该 yml配置
* Log4jContextSelector: org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
*/
public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor {
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Resource path = new ClassPathResource("com/example/myapp/config.yml");
PropertySource<?> propertySource = loadYaml(path);
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadYaml(Resource path) {
if (!path.exists()) {
throw new IllegalArgumentException("Resource " + path + " does not exist");
}
try {
return this.loader.load("custom-resource", path).get(0);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
}
}
}
上面的无效,下面的 添加启动过程的监听器,依然无效,原理一样,都是修改 springboot的环境变量,为时已晚。 log4j2 这之前就 都加载配置,启动完成了。
/**
* 自定义 监听器,在使用环境变量 之前,修改或者新增环境变量
* 记得 把这个 监听器,加入springboot 中 。否则这个监听器的代码不会执行 SpringApplication.addListeners(…) method or the SpringApplicationBuilder.listeners(…)
* 参考 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-events-and-listeners
*
* 另外, implements EnvironmentAware 也能获取到 环境变量
*/
@Slf4j
public class Application2EnvPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
// 在这里设置 log4j2 是无法生效的,因为log4j2 的启动和加载更早
// 生效有2法:1 命令行启动参数加上 -D上面的命令 ; 或者 2 添加 外置文件 log4j2.component.properties ,设置好参数。
setPropertyIfAbsent(env,"Log4jContextSelector","org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
}
private void setPropertyIfAbsent(ConfigurableEnvironment environment, String propertyName,Object value) {
String property = environment.getProperty(propertyName);
if (StringUtils.isBlank(property)) {
String envType = "systemProperties"; // 默认设置为 JVM 环境变量
setProperty(environment, envType, propertyName, value);
log.info("set property ok, {}={}", propertyName, environment.getProperty(propertyName));
return;
}
log.warn("failed to set property [{}={}] ,because {}={}", propertyName, value, propertyName, property);
}
}
5) 原理:log4j2 启动的时候,会加载 这个 目录下的该文件。代码在 org.apache.logging.log4j.util.PropertiesUtil
初始化 log4jContextFactory的时候,会 加载我们写好的那个属性值 创建 logFatory 实例 ; 如果没有找到 属性值,那么就会创建一个默认的,自然就是 同步的logger了 (这块我没有深究,请读者子自行去追踪一下)。
代码在 org.apache.logging.log4j.core.impl.Log4jContextFactory
怎么验证是否正确开启了全局异步功能
原理:开启异步后,肯定会加载 jar包 执行 disruptor的 代码的,这是高性能的关键api代码。所以 ,
验证方法1:我们只需要 把 pom中 的 这个依赖关闭,看看项目是否依然能正常启动,就知道 是否 开启了 log4j2的异步功能了。
假如注释掉 disruptor jar包,项目依然能 正常启动,则 表示 log4j2的 全局异步功能并没有开启!
假如注释掉 disruptor 的jar包后,启动报错如下,恭喜你,表示 正确开启了全局功能了。此时只需要 取消注释 disruptor的jar包,就可以 使用 真正的全局异步高性能了。
注意: 修改 pom文件后,可能不能立刻生效,请确保开启了 reload project after any changes 如图
验证方法2: 代码验证 ,在 springboot 启动类的 第一行,执行静态代码块 断言。prod 生产环境,不推荐使用断言,因为无法tcy catch 断言的异常,会导致项目启动失败。
该段代码来源于 log4j2的源码,org.apache.logging.log4j.core.async.AsyncLoggerContextSelector#isSelected
static {
Assert.isTrue(AsyncLoggerContextSelector.isSelected(), "log4j2 的异步 disruptor启动失败");
}
参考来源: 感谢 stackoverflow 的大神解答,非常高效,答案也很精准。
setting Log4jContextSelector system property for asynchronous logging
Does setting Property of Log4jContextSelector make any difference