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.
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.
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.