Skip to content

Keep the AsyncLogger location when the ring buffer is full - #4470

Open
ppkarwasz wants to merge 2 commits into
2.xfrom
fix/2.x/async-logger-location-fallback
Open

ppkarwasz wants to merge 2 commits into
2.xfrom
fix/2.x/async-logger-location-fallback

Conversation

@ppkarwasz

@ppkarwasz ppkarwasz commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #4340.

When the ring buffer is full, AsyncLogger may log an event in the current thread (the SYNCHRONOUS route of the queue-full policy, or a recursive logging call). logMessageInCurrentThread() had no location parameter, so:

  • a location already computed by AsyncLogger was dropped and computed again by LoggerConfig,
  • a location supplied with LogBuilder.withLocation() was replaced by the computed one.

Changes

  • logMessageInCurrentThread() receives the location and logs through Logger.log(…, location, …), which uses the location-aware reliability strategy.
  • A missing location is resolved once, in the two entry points of the logger: log(…, location, …) and logMessage(fqcn, …). This is the same stack depth as Compute AsyncLogger location when the caller passes null #4340.
  • The translator methods with and without a location (TranslatorType.log, logWithThreadLocalTranslator, initTranslator, logWithVarargTranslator) are merged: the methods without a location only differed by computing it.
  • All the methods on the thread-local translator path stay under the 35 bytes of bytecode inlined by default (the largest is logWithThreadLocalTranslator with 34 bytes).

Tests

AsyncLoggerQueueFullLocationTest fills the ring buffer, uses a queue-full policy that always returns SYNCHRONOUS and checks the location of events logged with an explicit location, with a log builder and with a classic logging call, for both the thread-local and the vararg translator. It fails on 2.x, where the explicit location is replaced.

Benchmarks

AsyncLoggersLocationBenchmark gets two new benchmarks: a log builder without a location (throughputLogBuilder) and with a precomputed location (throughputPrecomputedLocation).

Comparing this PR with 2.x (16 threads, thread-local and vararg translator), the differences in the benchmarks that compute a location stay within the run-to-run noise of my machine (about ±5 %): their sign flips if the order of the runs is swapped. The benchmarks without a location and with a precomputed location are limited by the consumer thread at 16 threads and show no difference.

AsyncLoggerUncachedThreadNameTest failed in some of my test-disruptor-4 runs with this PR (it passed in the single run I did on 2.x). It passes when run alone, and it shares target/AsyncLoggerTest.log with other test classes that run in parallel forks (forkCount=1C), which is the likely cause.

🤖 Generated with Claude Code

When the ring buffer is full, `AsyncLogger` may log an event in the
current thread. That path dropped the location computed or supplied by
the caller, so `LoggerConfig` computed it again, and a location supplied
with `LogBuilder.withLocation()` was replaced.

- Pass the location to `logMessageInCurrentThread()` and log through
  the location-aware reliability strategy.
- Resolve a missing location once, in the two entry points of the
  logger, and merge the translator methods with and without a location.
- Add benchmarks for log builders with and without a precomputed
  location.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
ppkarwasz added a commit that referenced this pull request Nov 9, 2026
…)"

This reverts commit e66562c.

The fix is reworked in #4470, which also keeps the location when the
ring buffer is full. It will be ported to `2.26.x` once merged.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@ramanathan1504 ramanathan1504 added async Affects asynchronous loggers or appenders bug Incorrect, unexpected, or unintended behavior of existing code java Pull requests that update Java code labels Nov 10, 2026
@ramanathan1504 ramanathan1504 added this to the 2.27.0 milestone Nov 10, 2026
@Override
public void append(final LogEvent event) {
if (Thread.currentThread() == testThread) {
locations.put(event.getMessage().getFormattedMessage(), event.getSource());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursion branch in both handleRingBufferFull methods passes the location too, but no test reaches it. A nested call from this appender while the buffer is full goes through that branch:

                    locations.put(event.getMessage().getFormattedMessage(), event.getSource());
                    if ("explicit".equals(event.getMessage().getFormattedMessage())) {
                        context.getLogger(AsyncLoggerQueueFullLocationTest.class)
                                .atInfo()
                                .withLocation(NESTED_LOCATION)
                                .log("nested");
                    }

plus a NESTED_LOCATION constant and "nested" in containsOnlyKeys with its own assertion.

public void logMessage(
final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) {
getTranslatorType().log(fqcn, level, marker, message, thrown);
getTranslatorType().log(fqcn, calcLocationIfRequested(fqcn), level, marker, message, thrown);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExtendedLoggerWrapper reaches this entry, because its requiresLocation() is false. The async location tests all go through log(…, location, …), so this line has no test. Can AsyncLoggerNullLocationTest get a case that logs through an ExtendedLoggerWrapper and checks the source on the enqueue route?

type="fixed">
<issue id="4470" link="https://github.com/apache/logging-log4j2/pull/4470"/>
<description format="asciidoc">
Keep the caller location of events that asynchronous loggers log synchronously, because the ring buffer is full.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorter line for the release notes:

Suggested change
Keep the caller location of events that asynchronous loggers log synchronously, because the ring buffer is full.
Keep the async logger location when the ring buffer is full.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

async Affects asynchronous loggers or appenders bug Incorrect, unexpected, or unintended behavior of existing code java Pull requests that update Java code port-to-2.26.x port-to-main

Projects

Development

Successfully merging this pull request may close these issues.

2 participants