joepembe opened a new issue, #3929:
URL: https://github.com/apache/logging-log4j2/issues/3929
## Description
The `ThrowableStackTraceRenderer` class can throw a `NullPointerException`
if the suppressed exceptions associated with the `Throwable` it is rendering
are being concurrently mutated. This is happening because
`ThrowableStackTraceRenderer` invokes `Throwable#getSuppressed()` twice: once
in `ThrowableStackTraceRenderer.Context.Metadata#populateMetadata()`, and a
second time in `ThrowableStackTraceRenderer#renderThrowable()`, ahead of
invoking `ThrowableStackTraceRenderer#renderSuppressed()`. If a racing thread
manages to add a new suppressed exception to the being-logged exception between
these two invocations, then the `Map<Throwable, Context.Metadata>` constructed
by `populateMetadata()` will contain no mapping for the newly-added
suppression, and as a result the dereference performed on line 168 explodes.
I have a repro below. To make the issue easier to detect, this repro does
not use async logging, and I've configured the logger with
`ignoreExceptions="false"` so that the NPE bubbles up to the logging site.
However, it is worth noting that in an async logging setup, the appender
responsible for invoking `ThrowableStackTraceRenderer` would run in the LMAX
disruptor thread (rather than the application thread that submitted the log
event), which likely means that this issue would occur if an application that
uses async logging performed the following sequence of operations in a single
thread:
1. Generate an exception, then
2. Log that exception, then
3. Perform an operation that resulted in the attachment of a new suppressed
exception to the logged exception.
## Configuration
**Version:** 2.25.1
**Operating system:** mac OS
**JDK:** Java 21
## Logs
```
Log4jBugTest > test() STANDARD_ERROR
2025-09-12T16:09:59.750535Z Test worker ERROR An exception occurred
processing Appender CONSOLE
java.lang.NullPointerException: Cannot read field "stackLength" because
"metadata" is null
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderStackTraceElements(ThrowableStackTraceRenderer.java:168)
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:110)
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderSuppressed(ThrowableStackTraceRenderer.java:133)
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:111)
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:87)
at
org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:59)
at
org.apache.logging.log4j.core.pattern.ThrowablePatternConverter.format(ThrowablePatternConverter.java:130)
at
org.apache.logging.log4j.core.layout.PatternLayout$NoFormatPatternSerializer.toSerializable(PatternLayout.java:354)
at
org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:251)
at
org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:237)
at
org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
at
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:227)
at
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:220)
at
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:211)
at
org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:160)
at
org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:133)
at
org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:124)
at
org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:88)
at
org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:711)
at
org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:669)
at
org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:645)
at
org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:589)
at
org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:92)
at org.apache.logging.log4j.core.Logger.log(Logger.java:187)
at
org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2970)
at
org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2922)
at
org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2904)
at
org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2682)
at
org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:2435)
at
org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1779)
at Log4jBugTest.test(Log4jBugTest.java:34)
```
## Reproduction
```
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
public class Log4jBugTest {
private static final Logger log =
LogManager.getLogger(Log4jBugTest.class);
@Test
public void test() throws InterruptedException, ExecutionException {
try (ExecutorService executor = Executors.newSingleThreadExecutor())
{
for (int i = 0; i < 1000; i++) {
AtomicBoolean done = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
Exception e = new Exception("root-" + i);
Future<?> f = executor.submit(() -> {
latch.countDown();
while (!done.get()) {
e.addSuppressed(new Exception("suppressed"));
}
});
latch.await();
log.info("The error", e);
done.set(true);
f.get();
}
}
}
}
```
And my log4j2 config:
```
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern= "%d{dd MMM yyyy HH:mm:ss,SSS} [%p] (%t)
%c: %m%n%ex"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="CONSOLE" />
</Root>
</Loggers>
</Configuration>
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]