用于低延迟日志的异步日志记录器

异步日志记录可以通过在单独的线程中执行I/O操作来提高应用程序的性能。

Log4j 2在这方面做了许多改进。

异步记录器是Log4j 2中新增的功能。它们的目的是尽快从对Logger.log的调用返回到应用程序。

您可以选择让所有的logger都是异步的,或者混合使用同步和异步的logger。将所有记录器设置为异步将提供最佳性能,而混合将为您提供更多灵活性。

LMAX破坏者技术。异步记录器在内部使用无锁的线程间通信库Disruptor,而不是队列,从而获得更高的吞吐量和更低的延迟。

作为异步日志记录器工作的一部分,异步追加器已被增强为在批处理结束时(当队列为空时)刷新到磁盘。这将产生与配置“immediateFlush=true”相同的结果,即所有接收到的日志事件始终在磁盘上可用,但效率更高,因为它不需要在每个日志事件上都访问磁盘。(Async Appenders在内部使用ArrayBlockingQueue,不需要在类路径中使用disruptor jar。)

权衡

尽管异步日志记录可以提供显著的性能优势,但在某些情况下,您可能希望选择同步日志记录。本节描述异步日志记录的一些优缺点。

好处

更高的峰值吞吐量。使用异步记录器,应用程序可以以同步记录器的6 - 68倍的速率记录消息。

这对于偶尔需要记录突发消息的应用程序来说特别有趣。异步日志记录可以通过缩短记录下一条消息之前的等待时间来帮助防止或抑制延迟峰值。如果队列大小配置得足够大,可以处理突发事件,那么异步日志记录将有助于防止应用程序在活动突然增加时(尽可能多地)落后。

更低的日志响应时间延迟。响应时间延迟是在给定工作负载下调用Logger.log返回所花费的时间。

异步记录器的延迟始终低于同步记录器,甚至低于基于队列的异步追加器。

缺点

错误处理。如果在日志记录过程中发生问题并抛出异常,异步日志记录器或追加程序就不太容易向应用程序发出此问题的信号。

这可以通过配置ExceptionHandler部分缓解,但这可能仍然不能涵盖所有情况。由于这个原因,如果日志记录是业务逻辑的一部分,例如,如果您使用Log4j作为审计日志记录框架,我们建议同步记录这些审计消息。(注意,您仍然可以将它们组合起来,除了使用同步日志记录审计跟踪之外,还可以使用异步日志记录调试/跟踪日志。)

在某些罕见的情况下,必须小心处理可变消息。大多数时候你不需要担心这个。Log4将确保日志消息(如logger.debug(“My object is {}”, myObject))在调用logger.debug()时使用myObject参数的状态。即使后来修改了myObject,日志消息也不会改变。异步记录可变对象是安全的,因为Log4j内置的大多数Message实现都会获取参数的快照。但是也有一些例外:MapMessage和StructuredDataMessage在设计上是可变的:在创建消息对象之后,可以向这些消息添加字段。这些消息在被异步记录器或异步追加器记录之后不应该被修改;您可能会在结果日志输出中看到修改,也可能不会看到。类似地,自定义消息实现在设计时应该考虑异步使用,并在构造时获取其参数的快照,或者记录其线程安全特征。

如果您的应用程序运行在CPU资源稀缺的环境中,例如只有一个单核CPU的机器,那么启动另一个线程不太可能提供更好的性能。

如果应用程序记录消息的持续速度快于底层appender的最大持续吞吐量,则队列将被填满,应用程序最终将以最慢的appender的速度进行日志记录。

如果发生这种情况,请考虑选择更快的appender,或者减少日志记录。如果这两种方法都不可行,那么通过同步记录可以获得更好的吞吐量和更少的延迟峰值。

使所有记录器异步

Log4j-2.9及更高版本需要类路径上的disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。

这是最简单的配置,并提供最好的性能。要使所有记录器异步,请将中断jar添加到类路径中,并设置系统属性 log4j2.contextSelectororg.apache.logging.log4j.core.async.AsyncLoggerContextSelectororg.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector

默认情况下,异步日志记录器不会将位置传递给I/O线程。如果您的布局或自定义过滤器之一需要位置信息,您需要在所有相关记录器(包括根记录器)的配置中设置“inclelocation =true”。

不需要位置的配置可能如下所示:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Don't forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
or
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector
     to make all loggers asynchronous. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="async.log" immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <Root level="info" includeLocation="false">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

当使用AsyncLoggerContextSelector或BasicAsyncLoggerContextSelector使所有记录器异步时,请确保在配置中使用正常的 <root><logger> 元素。

上下文选择器将确保所有日志记录器都是异步的,使用的机制不同于配置 <asyncRoot><asyncLogger> 时发生的情况。

后一种元素用于混合异步和同步日志记录器。如果您同时使用这两种机制,您最终将拥有两个后台线程,其中应用程序将日志消息传递给线程A,线程A将消息传递给线程B,然后线程B最终将消息记录到磁盘上。这是可行的,但是中间会有一个不必要的步骤。

您可以使用一些系统属性来控制异步日志子系统的各个方面。其中一些可用于调优日志记录性能。

还可以通过创建一个名为log4j2.component.properties的文件并将该文件包含在应用程序的类路径中来指定以下属性。

注意,在Log4j 2.10.0中,系统属性被重命名为更一致的样式。所有旧的属性名仍然被支持,在这里有文档说明。

混合同步和异步记录器

Log4j-2.9及更高版本需要类路径上的disruptor-3.3.4.jar或更高版本。

在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。不需要将系统属性“Log4jContextSelector”设置为任何值。

同步和异步记录器可以在配置中组合使用。这为您提供了更大的灵活性,但代价是性能上的轻微损失(与使所有记录器都异步相比)。

使用 <asyncRoot><asyncLogger> 配置元素来指定需要异步的日志记录器。

一个配置只能包含一个根日志记录器( <root><asyncRoot> 元素),否则可以将异步和非异步日志记录器组合在一起。例如,包含 <asyncLogger> 元素的配置文件也可以包含用于同步日志记录器的 <root><logger> 元素。

默认情况下,异步日志记录器不会将位置传递给I/O线程。如果您的布局或自定义过滤器之一需要位置信息,您需要在所有相关记录器(包括根记录器)的配置中设置“inclelocation =true”。

混合异步日志记录器的配置可能是这样的:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- No need to set system property "log4j2.contextSelector" to any value
     when using <asyncLogger> or <asyncRoot>. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
              immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <!-- pattern layout actually uses location, so we need to include it -->
    <AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </AsyncLogger>
    <Root level="info" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

自定义WaitStrategy

上面提到的系统属性只允许从一组固定的预定义WaitStrategies中进行选择。

在某些情况下,您可能希望配置一个不在此列表中的自定义WaitStrategy。这可以通过在Log4j配置中使用AsyncWaitStrategyFactory元素来实现。

配置自定义WaitStrategy的配置如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
 
  <AsyncWaitStrategyFactory
      class="my.custom.AsyncWaitStrategyFactory" />
 
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
    </File>
  </Appenders>
  <Loggers>
    <AsyncRoot level="info">
      <AppenderRef ref="MyFile"/>
    </AsyncRoot>
  </Loggers>
</Configuration>

自定义的实现,需要实现对应的接口:

public interface AsyncWaitStrategyFactory {
  /**
  * Returns a non-null implementation of the LMAX Disruptor's WaitStrategy interface.
  * This WaitStrategy will be used by Log4j Async Loggers and Async LoggerConfigs.
  *
  * @return the WaitStrategy instance to be used by Async Loggers and Async LoggerConfigs
  */
  WaitStrategy createWaitStrategy();
}

Location 位置,位置,位置……

如果其中一个布局配置了与位置相关的属性,如HTML locationInfo,或模式%C或$class、%F或%file、%l或%location、%l或%line、%M或%method之一,Log4j将获取堆栈的快照,并遍历堆栈跟踪以查找位置信息。

这是一个代价高昂的操作:对于同步记录器来说速度要慢1.3 - 5倍。

同步记录器在获取堆栈快照之前尽可能等待较长的时间。如果不需要指定位置,则永远不会快照。

然而,异步日志记录器需要在将日志消息传递给另一个线程之前做出这个决定;在此之后,位置信息将丢失。

对异步记录器来说,堆栈跟踪快照对性能的影响甚至更高:有位置记录比没有位置记录慢30-100倍。

因此,默认情况下,异步记录器和异步追加器不包含位置信息。

您可以通过指定inclelocation =”true”来覆盖日志记录器或异步appender配置中的默认行为。

参考资料

https://logging.apache.org/log4j/2.x/manual/async.html