How Switching to Async Log4j2 Can Double Your Application Throughput

This article analyzes a performance bottleneck caused by synchronous Log4j2 logging, demonstrates how converting to asynchronous logging with AsyncLogger or AsyncAppender dramatically reduces response time, increases TPS, and lowers CPU usage, and provides detailed configuration examples and best‑practice guidelines.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
How Switching to Async Log4j2 Can Double Your Application Throughput

1. Problem Review

1.1 Problem Description

During performance testing, the response time of the interface grew with increasing concurrency, throughput stopped increasing, and CPU usage was high.

1.2 Analysis Approach

Which component caused the high CPU and blocked TPS growth? What does the call‑chain latency distribution look like? Are there any slow points?

1) A flame‑graph showed that Log4j2 logging consumed about 40% of CPU, suggesting Log4j2 as the culprit.

2) Call‑chain analysis with pfinder showed a total latency of 78 ms with no obvious slow method or SQL, ruling out code‑level issues.

1.3 Preliminary Conclusion

The problem lies in Log4j2 configuration; a detailed review of log4j2.xml is required.

The Loggers node and its child nodes are configured for synchronous logging. Synchronous logging blocks the business thread when many logs are written. Switching to asynchronous logging can improve performance.

1.4 Regression Verification

After changing to asynchronous logging, TP99 dropped from 51 ms to 23 ms, TPS increased from 493 to 1078, and CPU usage fell from 82 % to 57 %.

Problem solved, but further study of Log4j2 logging is still needed.

1.5 Final Conclusion

Using Log4j2 asynchronous logging greatly improves performance and reduces impact on the application.

It also reduces unnecessary log output.

2. Log4j2 Logging

2.1 Advantages of Log4j2

Better exception handling compared with Logback.

Significant performance gains over Log4j 1.x and Logback.

Automatic configuration reload without restarting the application.

Garbage‑free design that minimizes JVM GC pressure.

2.2 Log4j2 Logging Types

Log4j2 supports synchronous and asynchronous logging. Asynchronous logging can be implemented via AsyncAppender or AsyncLogger . Three modes exist: fully asynchronous, mixed, and synchronous, with performance ranking from high to low.

2.3 Synchronous Logging

In synchronous mode, the logging statement runs in the same thread as the business logic, so the thread must wait for the log to be written before continuing.

2.4 Asynchronous Logging

In asynchronous mode, a dedicated thread handles log output, allowing the main thread to continue processing business logic without waiting.

Log4j2 implements asynchronous logging via two mechanisms:

AsyncAppender uses an ArrayBlockingQueue to buffer log events.

AsyncLogger uses the LMAX Disruptor framework for high‑throughput, lock‑free processing.

2.4.1 AsyncAppender

AsyncAppender references another appender; when a log event arrives, a separate thread processes it. Exceptions in the appender are not visible to the application. It defaults to ArrayBlockingQueue and does not require external libraries.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
  <Appenders>
    <!-- Normal RollingFile appender -->
    <RollingFile name="RollingFileError" fileName="${Log_Home}/error.${date:yyyy-MM-dd}.log" immediateFlush="true">
      <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %logger{36} : %msg%xEx%n"/>
      <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
      <Policies>
        <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
    </RollingFile>
    <!-- AsyncAppender that references the RollingFile -->
    <Async name="Async">
      <AppenderRef ref="RollingFileError"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

2.4.2 AsyncLogger

AsyncLogger is the recommended way. It returns from Logger.log faster because the actual I/O is handled by the Disruptor framework.

2.5 Using Asynchronous Logging

Two deployment options exist:

Global Asynchronous : All logs are asynchronous. Enable by adding the JVM property

log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

and a log4j2.component.properties file.

Mixed Asynchronous : Synchronous and asynchronous loggers coexist. Configure specific loggers with AsyncLogger in log4j2.xml.

Example Maven dependency for Disruptor:

<dependency>
  <groupId>com.lmax</groupId>
  <artifactId>disruptor</artifactId>
  <version>3.4.2</version>
</dependency>

Mixed configuration example (excerpt):

<Configuration status="WARN" monitorInterval="30">
  <Properties>
    <Property name="logFilePath">log</Property>
    <Property name="logFileName">test.log</Property>
  </Properties>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
    </Console>
    <RollingFile name="RollingFileInfo" fileName="${logFilePath}/${logFileName}">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
      <Policies>
        <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>
    <!-- other appenders ... -->
  </Appenders>
  <Loggers>
    <Logger name="org.springframework" level="INFO"/>
    <Logger name="org.mybatis" level="INFO"/>
    <Root level="all">
      <AppenderRef ref="Console"/>
      <AppenderRef ref="RollingFileInfo"/>
    </Root>
    <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="false">
      <AppenderRef ref="RollingFileError"/>
    </AsyncLogger>
  </Loggers>
</Configuration>

2.6 Precautions When Using Asynchronous Logging

Do not use AsyncAppender and AsyncLogger together in the same configuration; the performance gain disappears.

Avoid mixing global synchronous logging with asynchronous components.

Set immediateFlush="false" unless strict ordering is required.

Avoid enabling location information (e.g., %C, %F, %L) because capturing stack traces adds significant overhead.

3. Summary

Thorough root‑cause analysis of performance issues, especially when using third‑party components like Log4j2, is essential. Switching to asynchronous logging can dramatically improve response time, increase throughput, and lower CPU consumption, but it must be configured carefully and combined with best‑practice logging hygiene.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Performance Optimizationlog4j2asynchronous logginglogging configuration
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.