Backend Development 13 min read

Why Does Log4j2 Async Logging Block Threads? Deep Dive & Solutions

Log4j2’s asynchronous logging can cause thread blocking when the Disruptor ring buffer fills, a problem explored through its architecture, root causes, and practical mitigation strategies such as dual‑track log classification, bytecode‑enhanced line‑level control, Maven plugins, and IDE integrations for dynamic log management.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Why Does Log4j2 Async Logging Block Threads? Deep Dive & Solutions

Existing Log Printing Situation

Logging is a critical infrastructure in software engineering for monitoring, diagnosing exceptions, and tracing behavior. Apache Log4j2 is a mainstream logging framework offering modular architecture and extensibility, but misunderstanding its asynchronous mechanism, buffer strategies, or mismatched configuration can cause I/O blocking, excessive memory consumption, and performance bottlenecks.

Log Blocking

Thread blocking caused by logging is common; a typical symptom is many threads stuck in jstack showing the stack trace below.

From Debug to Production: Choosing a Log Strategy

During development, detailed logs (input/output parameters, call chains, auxiliary information) are essential for debugging and verification. In production, excessive logs degrade performance and obscure critical business traces. Deciding which logs to keep involves balancing observability with runtime efficiency.

Root Causes of the Problem

Log Printing Principle Analysis

In our application we use Log4j2’s asynchronous configuration. A log event passes through the Log4j2 façade, is filtered and wrapped, then placed into a Disruptor ring buffer. A single consumer thread dequeues events and writes them to the target file.

Disruptor Initialization

When LoggerContext starts, each AsyncLoggerConfig initializes its Disruptor via start() . The Disruptor is a ring buffer with many performance optimizations; the default size is 256 KB.

Queue Full Leads to Blocking

The ring buffer has a fixed size. When it becomes full, the default behavior AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=true makes producer threads wait for a free slot, acquiring a global lock and causing thread blockage.

Fundamental Reason

The producer’s speed exceeds the consumer’s speed. If the underlying appender (e.g., FileAppender ) writes slowly due to high disk I/O, the consumer cannot keep up, leading to queue buildup. Frequent native flush calls exacerbate the issue.

Mitigation Strategies

Solution Selection

Two directions: improve the producer side or the consumer side. Basic optimizations include increasing queue capacity (watch for OOM risk). The core challenge is balancing log detail with system stability.

We propose a dual‑track log classification:

Functional logs (mandatory) : business-critical events, ensuring observability.

Diagnostic logs (optional) : RPC parameters, debug traces, enabled on demand.

This hierarchy allows stable core logging under high concurrency while flexibly controlling auxiliary logs.

Technical Choices

Fine‑grained line‑level control is needed. Traditional global level filters (INFO/WARN/ERROR) are too coarse. We suggest dynamic, per‑line enable/disable mechanisms.

Distinguish Necessary vs. Unnecessary Logs

Custom wrapper illustrated below:

Line‑Level Control Approaches

1. Custom Appender filter that parses stack traces to match a target line number and discards non‑matching events.

2. Manually embed line information in log messages, e.g., LogUtils.debug(()->log.info("业务日志")) , though this lacks scalability.

3. Compile‑time injection: use bytecode manipulation (ASM) to add class‑line metadata to log calls, then evaluate at runtime.

4. IDE plugin integration: an IntelliJ plugin reports the enable/disable state of specific log lines to a central configuration (e.g., Apollo), allowing real‑time toggling without redeployment.

Implementation Details

Maven Compilation Plugin

During the process-classes phase, the plugin modifies compiled bytecode to embed class and line information, eliminating runtime overhead of stack trace analysis.

IDE Plugin

The plugin reports the on/off status of targeted log lines to Apollo, providing a UI for developers to toggle logs instantly.

Overall Workflow

Compile the project with the Maven plugin, use the IDE plugin to control log activation, store the state in Apollo, and let a custom logger read the configuration to decide whether to emit each log entry.

Conclusion

In modern distributed systems, log management has become a cornerstone of observability. This article identified the root cause of Log4j2 async logging blockage—ring‑buffer saturation—and proposed a dual‑track log governance model combined with bytecode‑level line control and IDE‑driven toggling, achieving both stability and fine‑grained diagnostic capability.

javaPerformance Optimizationlog4j2thread blockingasynchronous logging
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login 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.