高性能 log4j 2 疑难杂症 - 怎样正确开启全局异步 怎么验证是否真正开启了

1 篇文章 0 订阅
1 篇文章 0 订阅

核心参数 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 。

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context

/**
* 自定义一个 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

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值