Backend Development 15 min read

Analyzing Log4j2 Asynchronous Logging Blocking and Strategies for Fine-Grained Log Control

This article examines the causes of Log4j2 asynchronous logging blockage in high‑throughput Java services, explains the underlying Disruptor mechanics, and proposes a dual‑track logging architecture with compile‑time bytecode enhancement and IDE plugins for line‑level log activation.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
Analyzing Log4j2 Asynchronous Logging Blocking and Strategies for Fine-Grained Log Control

1 Current Log Printing Situation

Logging is a critical infrastructure in software engineering for monitoring, diagnosing exceptions, and tracing behavior. Apache Log4j2, a mainstream logging framework, offers modular and extensible multi‑dimensional log management, but misuse of its asynchronous mechanism, buffer strategies, or mismatched configuration can cause I/O blocking, excessive memory consumption, and severe performance bottlenecks.

1 Log Blocking

Thread blocking caused by logging is a common issue; when many threads are blocked, a typical jstack snapshot shows numerous threads waiting on logging resources.

2 From Debug to Production, Choosing Log Strategy

During development, detailed logs (full request/response, call chains, auxiliary information) are indispensable for debugging and verification. In production, excessive logs degrade performance and drown out critical business traces, creating a dilemma between observability and runtime efficiency.

The trade‑off reflects the perpetual balance between system observability and operational performance.

2 Causes of the Problem

2.1 Log Printing Principle Analysis

In the used Log4j2 configuration, asynchronous logging works as follows: multiple threads invoke Log4j2’s façade, the log event passes through filters and wrappers, then is placed into a Disruptor ring buffer. A dedicated consumer thread drains the buffer and writes to the target file.

2.2 Initialization of log4j2 Disruptor

When the LoggerContext starts, each AsyncLoggerConfig calls start() to initialise its Disruptor. The Disruptor is a high‑performance ring buffer (default size 256 KB in the provided log4j.xml).

2.3 Queue Full Leading to Log Blocking

The Disruptor ring buffer has a fixed capacity; its publishing logic is shown below.

When the queue is full, the default behavior AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=true makes producer threads wait for a free slot, acquiring a global lock and causing thread blockage as observed in the stack trace.

2.4 Root Cause

Producer speed > consumer speed: the AsyncAppender’s background thread consumes events and forwards them to the actual Appender (e.g., FileAppender). If the Appender’s write speed is slow (high disk I/O), the consumer cannot keep up, leading to queue buildup. Frequent native flush calls exacerbate the problem.

3 Solutions

3.1 Solution Selection

The problem can be tackled from two angles—producer side and consumer side. The diagram below outlines the high‑level actions.

Beyond basic queue‑size tuning (which risks OOM), the core issue is balancing log detail with system stability. Over‑logging risks blockage; over‑pruning hampers troubleshooting.

We propose a two‑track log classification:

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

Diagnostic logs (optional): RPC parameters, debug traces, enabled dynamically as needed.

This layered strategy guarantees stable output of essential logs under high concurrency while allowing flexible control of auxiliary logs, achieving both operational efficiency and performance.

3.2 Technical Choices

Traditional global level filters (INFO/WARN/ERROR) are too coarse. An ideal solution provides line‑level granularity, enabling or disabling individual log statements without code changes or restarts.

Such fine‑grained control empowers developers to capture full context for critical paths, focus on specific call lifecycles, or instantly activate deep diagnostics in emergencies.

Implementation ideas include:

3.2.1 Distinguish necessary and unnecessary log printing

A custom wrapper is illustrated below.

3.2.2 How to control non‑essential logs at line level

1. Custom Appender filter:

By parsing the stack trace of a log call, the filter can decide whether the current line number matches a predefined whitelist; otherwise the event is discarded. This approach suffers from unreliable stack extraction in lambda expressions, performance overhead from frequent stack traversal, and limited integration with existing log level hierarchies.

2. Capture line information before logging:

Manually embed line data in the log message, e.g., log.info("[MyClass:133] business log") . This method is simple but lacks scalability and observability.

3. Capture line information at compile time:

Using bytecode manipulation (ASM), transform calls such as LogUtils.debug(()->log.info("业务日志")) into LogUtils.debug("Class+Line", ()->log.info("业务日志")) , allowing runtime checks based on compiled metadata.

Bytecode technology choice: ASM is selected for its flexibility and performance characteristics.

4. Free control of enable/disable:

An IDE plugin reports the on/off state of a specific log line to a central configuration service (Apollo). The custom log utility reads this configuration to decide whether to emit the log.

3.3 Implementation

3.3.1 Maven compilation plugin

Goal: obtain class and line information for each log statement.

Runtime approach (enable includeLocation in Logger config) incurs heavy stack‑trace generation overhead, unsuitable for high‑QPS scenarios.

During Maven’s process-classes phase, the plugin modifies compiled bytecode to embed line metadata, eliminating runtime cost.

3.3.2 Idea plugin

Goal: precise control of whether a particular log line is printed.

The plugin reports the enable/disable status to Apollo without blocking the main thread, providing a timed refresh to keep the online control responsive.

3.3.3 Overall Process

Deploy the Maven plugin to embed line metadata, use the Idea plugin to toggle log lines, store the toggle state in Apollo, and let a custom log utility read the configuration to decide on emission.

4 Summary

In today’s increasingly complex distributed systems, log management has evolved from simple record‑keeping to a cornerstone of system observability. This article exposed the blocking issue caused by Log4j2’s asynchronous mechanism, identified queue saturation as the root cause, and proposed a comprehensive log governance redesign.

The proposed dual‑track architecture separates functional and diagnostic logs, while compile‑time bytecode enhancement and IDE‑driven toggles enable line‑level control. This "surgical" approach avoids the bluntness of traditional log‑level policies and equips high‑load services with elastic, fine‑grained observability.

About the author

Cai Menghui – Backend Engineer, Platform Technology Department

JavaPerformanceobservabilitylog4j2asynchronous loggingLogging Strategy
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

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.