This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new dc6c53ab7b Improve implementations of `LogEvent.toImmutable()` and 
`ReusableMessage.memento()` (#3171)
dc6c53ab7b is described below

commit dc6c53ab7bb195f3878d3ee92cd0ff20634326f1
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Fri May 16 16:11:29 2025 +0200

    Improve implementations of `LogEvent.toImmutable()` and 
`ReusableMessage.memento()` (#3171)
    
    The implementations of `LogEvent.toImmutable()` either use serialization or 
one of the `Log4jLogEvent.Builder` constructors and custom per class code.
    
    This PR:
    
    - Redirects all implementation of `LogEvent.toImmutable` to the 
`Builder(LogEvent)` constructor (except `Log4jLogEvent.toImmutable()`)
    - Improve the constructor to create really immutable and thread-safe 
instances.
    - Removes the usage of `ThrowableProxy` for purposes not related to 
serialization.
    - Fixes some implementations of `ReusableMessage.memento()`.
    - Uses `ReusableMessage.memento()` instead of `ImmutableMessage` where
      applicable.
---
 .../org/apache/log4j/rewrite/MapRewritePolicy.java |   1 -
 .../log4j/rewrite/PropertyRewritePolicy.java       |   1 -
 .../log4j/message/ReusableObjectMessage.java       |   6 +-
 .../message/ReusableParameterizedMessage.java      |   6 +-
 .../log4j/message/ReusableSimpleMessage.java       |   6 +-
 .../log4j/core/test/appender/ListAppender.java     |   8 +-
 .../logging/log4j/core/async/BlockingAppender.java |   3 +-
 .../log4j/core/async/RingBufferLogEventTest.java   |  45 ++--
 .../log4j/core/impl/MutableLogEventTest.java       |   2 +-
 .../logging/log4j/core/net/SmtpManagerTest.java    |  14 +-
 .../org/apache/logging/log4j/core/LogEvent.java    |   3 +
 .../logging/log4j/core/async/AsyncLogger.java      |   9 -
 .../log4j/core/async/AsyncLoggerConfig.java        |   2 +
 .../core/async/AsyncLoggerConfigDisruptor.java     |   6 +-
 .../log4j/core/async/RingBufferLogEvent.java       |  40 ++--
 .../core/async/RingBufferLogEventTranslator.java   |  16 +-
 .../logging/log4j/core/config/LoggerConfig.java    |   9 -
 .../logging/log4j/core/impl/Log4jLogEvent.java     | 228 ++++++++++++---------
 .../logging/log4j/core/impl/MutableLogEvent.java   |  54 ++++-
 .../log4j/core/impl/ReusableLogEventFactory.java   |  25 ++-
 .../log4j/core/layout/AbstractJacksonLayout.java   |   5 +-
 .../apache/logging/log4j/smtp/SmtpManagerTest.java |  14 +-
 src/changelog/.2.x.x/throwable-proxy-clean-up.xml  |   9 +
 23 files changed, 292 insertions(+), 220 deletions(-)

diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java
index f62f9fb0f6..5841920d67 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java
@@ -105,7 +105,6 @@ public class MapRewritePolicy implements RewritePolicy {
                         
.setThrown(source.getThrowableInformation().getThrowable())
                         .setTimeMillis(source.getTimeStamp())
                         .setNanoTime(0)
-                        .setThrownProxy(null)
                         .build();
             }
             return new LogEventAdapter(event);
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
 
b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
index fd99674f40..1fd4c9fcd5 100644
--- 
a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
+++ 
b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
@@ -110,7 +110,6 @@ public class PropertyRewritePolicy implements RewritePolicy 
{
                         
.setThrown(source.getThrowableInformation().getThrowable())
                         .setTimeMillis(source.getTimeStamp())
                         .setNanoTime(0)
-                        .setThrownProxy(null)
                         .build();
             }
             return new LogEventAdapter(event);
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java
index aed52d13c5..1b87239f40 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java
@@ -128,7 +128,11 @@ public class ReusableObjectMessage implements 
ReusableMessage, ParameterVisitabl
 
     @Override
     public Message memento() {
-        return new ObjectMessage(obj);
+        Message message = new ObjectMessage(obj);
+        // Since `toString()` methods are not always pure functions and might 
depend on the thread and other context
+        // values, we format the message and cache the result.
+        message.getFormattedMessage();
+        return message;
     }
 
     /**
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
index d3734eeedd..86f4bcf3d5 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
@@ -114,7 +114,11 @@ public class ReusableParameterizedMessage implements 
ReusableMessage, ParameterV
 
     @Override
     public Message memento() {
-        return new ParameterizedMessage(messagePattern, getTrimmedParams());
+        Message message = new ParameterizedMessage(messagePattern, 
getTrimmedParams());
+        // Since `toString()` methods are not always pure functions and might 
depend on the thread and other context
+        // values, we format the message and cache the result.
+        message.getFormattedMessage();
+        return message;
     }
 
     private void init(final String messagePattern, final int argCount, final 
Object[] args) {
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java
index 824e9d3a98..95f3321de6 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java
@@ -85,7 +85,11 @@ public class ReusableSimpleMessage implements 
ReusableMessage, CharSequence, Par
 
     @Override
     public Message memento() {
-        return new SimpleMessage(charSequence);
+        SimpleMessage message = new SimpleMessage(charSequence);
+        // Since `toString()` methods are not always pure functions and might 
depend on the thread and other context
+        // values, we format the message and cache the result.
+        message.getFormattedMessage();
+        return message;
     }
 
     // CharSequence impl
diff --git 
a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java
 
b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java
index 95c9fae195..f941db1267 100644
--- 
a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java
+++ 
b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java
@@ -35,7 +35,6 @@ import 
org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.impl.MutableLogEvent;
 import org.apache.logging.log4j.core.layout.SerializedLayout;
 import org.awaitility.Awaitility;
 
@@ -121,12 +120,7 @@ public class ListAppender extends AbstractAppender {
     public void append(final LogEvent event) {
         final Layout<? extends Serializable> layout = getLayout();
         if (layout == null) {
-            if (event instanceof MutableLogEvent) {
-                // must take snapshot or subsequent calls to logger.log() will 
modify this event
-                events.add(((MutableLogEvent) event).createMemento());
-            } else {
-                events.add(event);
-            }
+            events.add(event.toImmutable());
         } else if (layout instanceof SerializedLayout) {
             final byte[] header = layout.getHeader();
             final byte[] content = layout.toByteArray(event);
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
index ff45e5ce67..1185b5a758 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
@@ -33,7 +33,6 @@ import 
org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 
 /**
  * Appender that can be halted and resumed, for testing queue-full scenarios.
@@ -58,7 +57,7 @@ public class BlockingAppender extends AbstractAppender {
         // may be a reusable event, make a copy, don't keep a reference to the 
original event
         final List<LogEvent> events = logEvents;
         if (events != null) {
-            events.add(Log4jLogEvent.createMemento(event));
+            events.add(event.toImmutable());
         }
 
         if (countDownLatch == null) {
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
index 75559f8714..5377040565 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
@@ -28,6 +28,7 @@ import java.util.Arrays;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.ThreadContext.ContextStack;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.ThrowableProxy;
@@ -201,7 +202,7 @@ class RingBufferLogEventTest {
         final Level level = Level.TRACE;
         final Message data = new SimpleMessage("message");
         final Throwable t = new InternalError("not a real error");
-        final ContextStack contextStack = null;
+        final ContextStack contextStack = ThreadContext.getImmutableStack();
         final String threadName = "main";
         final StackTraceElement location = null;
         evt.setValues(
@@ -222,25 +223,31 @@ class RingBufferLogEventTest {
                 new DummyNanoClock(1));
         ((StringMap) evt.getContextData()).putValue("key", "value");
 
-        final RingBufferLogEvent other = 
SerialUtil.deserialize(SerialUtil.serialize(evt));
-        assertThat(other.getLoggerName()).isEqualTo(loggerName);
-        assertThat(other.getMarker()).isEqualTo(marker);
-        assertThat(other.getLoggerFqcn()).isEqualTo(fqcn);
-        assertThat(other.getLevel()).isEqualTo(level);
-        assertThat(other.getMessage()).isEqualTo(data);
-        assertThat(other.getThrown()).isNull();
-        assertThat(other.getThrownProxy()).isEqualTo(new ThrowableProxy(t));
-        assertThat(other.getContextData()).isEqualTo(evt.getContextData());
-        assertThat(other.getContextStack()).isEqualTo(contextStack);
-        assertThat(other.getThreadName()).isEqualTo(threadName);
-        assertThat(other.getSource()).isEqualTo(location);
-        assertThat(other.getTimeMillis()).isEqualTo(12345);
-        assertThat(other.getInstant().getNanoOfMillisecond()).isEqualTo(678);
+        final LogEvent other = 
SerialUtil.deserialize(SerialUtil.serialize(evt));
+        assertThat(other.getLoggerName()).as("Logger 
name").isEqualTo(loggerName);
+        assertThat(other.getMarker()).as("Marker").isEqualTo(marker);
+        assertThat(other.getLoggerFqcn())
+                .as("Fully qualified class name of logger implementation")
+                .isEqualTo(fqcn);
+        assertThat(other.getLevel()).as("Log event level").isEqualTo(level);
+        assertThat(other.getMessage()).as("Log event message").isEqualTo(data);
+        assertThat(other.getThrown()).as("Thrown exception").isNull();
+        assertThat(other.getThrownProxy())
+                .as("Serialization proxy for thrown exception")
+                .isEqualTo(new ThrowableProxy(t));
+        assertThat(other.getContextData()).as("Context data 
map").isEqualTo(evt.getContextData());
+        assertThat(other.getContextStack()).as("Context data 
stack").isEqualTo(contextStack);
+        assertThat(other.getThreadName()).as("Thread 
name").isEqualTo(threadName);
+        assertThat(other.getSource()).as("Log event 
location").isEqualTo(location);
+        assertThat(other.getTimeMillis()).as("Log event timestamp in 
millis").isEqualTo(12345);
+        assertThat(other.getInstant().getNanoOfMillisecond())
+                .as("Log event timestamp in nanos of millis")
+                .isEqualTo(678);
     }
 
     @SuppressWarnings("deprecation")
     @Test
-    void testCreateMementoReturnsCopy() {
+    void testToImmutableReturnsCopy() {
         final RingBufferLogEvent evt = new RingBufferLogEvent();
         final String loggerName = "logger.name";
         final Marker marker = MarkerManager.getMarker("marked man");
@@ -269,7 +276,7 @@ class RingBufferLogEventTest {
                 new DummyNanoClock(1));
         ((StringMap) evt.getContextData()).putValue("key", "value");
 
-        final LogEvent actual = evt.createMemento();
+        final LogEvent actual = evt.toImmutable();
         assertThat(actual.getLoggerName()).isEqualTo(evt.getLoggerName());
         assertThat(actual.getMarker()).isEqualTo(evt.getMarker());
         assertThat(actual.getLoggerFqcn()).isEqualTo(evt.getLoggerFqcn());
@@ -288,7 +295,7 @@ class RingBufferLogEventTest {
     }
 
     @Test
-    void testCreateMementoRetainsParametersAndFormat() {
+    void testToImmutableRetainsParametersAndFormat() {
         final RingBufferLogEvent evt = new RingBufferLogEvent();
         // Initialize the event with parameters
         evt.swapParameters(new Object[10]);
@@ -321,7 +328,7 @@ class RingBufferLogEventTest {
                     new DummyNanoClock(1));
             ((StringMap) evt.getContextData()).putValue("key", "value");
 
-            final Message actual = evt.createMemento().getMessage();
+            final Message actual = evt.toImmutable().getMessage();
             assertThat(actual.getFormat()).isEqualTo("Hello {}!");
             assertThat(actual.getParameters()).isEqualTo(new String[] 
{"World"});
             assertThat(actual.getFormattedMessage()).isEqualTo("Hello World!");
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
index 7f4ebd2d4b..092c2133e2 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
@@ -137,7 +137,7 @@ class MutableLogEventTest {
         assertEquals("msg in a bottle", memento.getFormattedMessage(), 
"formatted");
         assertArrayEquals(new String[] {"bottle"}, memento.getParameters(), 
"parameters");
 
-        final Message eventMementoMessage = 
mutable.createMemento().getMessage();
+        final Message eventMementoMessage = mutable.toImmutable().getMessage();
         assertEquals("msg in a {}", eventMementoMessage.getFormat(), "format");
         assertEquals("msg in a bottle", 
eventMementoMessage.getFormattedMessage(), "formatted");
         assertArrayEquals(new String[] {"bottle"}, 
eventMementoMessage.getParameters(), "parameters");
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.java
index c95e3aa891..5e3dd846a1 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.java
@@ -16,16 +16,13 @@
  */
 package org.apache.logging.log4j.core.net;
 
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.SmtpAppender;
 import org.apache.logging.log4j.core.async.RingBufferLogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
-import org.apache.logging.log4j.core.impl.MementoMessage;
 import org.apache.logging.log4j.core.impl.MutableLogEvent;
 import org.apache.logging.log4j.core.util.ClockFactory;
 import org.apache.logging.log4j.core.util.DummyNanoClock;
@@ -72,17 +69,14 @@ class SmtpManagerTest {
                 .setBufferSize(10)
                 .build();
         final MailManager mailManager = appender.getManager();
-        assertThat("is instance of SmtpManager", mailManager instanceof 
SmtpManager);
+        assertThat(mailManager).isInstanceOf(SmtpManager.class);
         final SmtpManager smtpManager = (SmtpManager) mailManager;
         smtpManager.removeAllBufferedEvents(); // in case this smtpManager is 
reused
         smtpManager.add(event);
 
         final LogEvent[] bufferedEvents = 
smtpManager.removeAllBufferedEvents();
-        assertThat("unexpected number of buffered events", 
bufferedEvents.length, is(1));
-        assertThat(
-                "expected the immutable version of the event to be buffered",
-                bufferedEvents[0].getMessage(),
-                is(instanceOf(MementoMessage.class)));
+        assertThat(bufferedEvents).as("Buffered events").hasSize(1);
+        assertThat(bufferedEvents[0].getMessage()).as("Immutable 
message").isNotInstanceOf(ReusableMessage.class);
     }
 
     // LOG4J2-3172: make sure existing protections are not violated
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
index 32001652bb..63daa9d3bb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
@@ -51,6 +51,7 @@ public interface LogEvent extends Serializable {
      * Returns an immutable version of this log event, which MAY BE a copy of 
this event.
      *
      * @return an immutable version of this log event
+     * @since 2.8.1
      */
     LogEvent toImmutable();
 
@@ -187,7 +188,9 @@ public interface LogEvent extends Serializable {
      * Gets throwable proxy associated with logging request.
      *
      * @return throwable, may be null.
+     * @deprecated since 2.25.0. This method should be replaced with {@link 
#getThrown()}.
      */
+    @Deprecated
     ThrowableProxy getThrownProxy();
 
     /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
index a4cdb55eb9..1b15ce3ed9 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
@@ -238,7 +238,6 @@ public class AsyncLogger extends Logger implements 
EventTranslatorVararg<RingBuf
 
         final RingBufferLogEventTranslator translator = getCachedTranslator();
         initTranslator(translator, fqcn, level, marker, message, thrown);
-        initTranslatorThreadValues(translator);
         publish(translator);
     }
 
@@ -266,7 +265,6 @@ public class AsyncLogger extends Logger implements 
EventTranslatorVararg<RingBuf
 
         final RingBufferLogEventTranslator translator = getCachedTranslator();
         initTranslator(translator, fqcn, location, level, marker, message, 
thrown);
-        initTranslatorThreadValues(translator);
         publish(translator);
     }
 
@@ -358,13 +356,6 @@ public class AsyncLogger extends Logger implements 
EventTranslatorVararg<RingBuf
                 );
     }
 
-    private void initTranslatorThreadValues(final RingBufferLogEventTranslator 
translator) {
-        // constant check should be optimized out when using default (CACHED)
-        if (THREAD_NAME_CACHING_STRATEGY == 
ThreadNameCachingStrategy.UNCACHED) {
-            translator.updateThreadValues();
-        }
-    }
-
     /**
      * Returns the caller location if requested, {@code null} otherwise.
      *
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
index c85fa7d993..92d351e2de 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
@@ -185,7 +185,9 @@ public class AsyncLoggerConfig extends LoggerConfig {
 
     private void populateLazilyInitializedFields(final LogEvent event) {
         event.getSource();
+        event.getThreadId();
         event.getThreadName();
+        event.getThreadPriority();
     }
 
     void logInBackgroundThread(final LogEvent event) {
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java
index ba5fd4d54a..eec1dbe05b 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java
@@ -367,14 +367,14 @@ public class AsyncLoggerConfigDisruptor extends 
AbstractLifeCycle implements Asy
         LogEvent logEvent = ensureImmutable(event);
         if (logEvent.getMessage() instanceof ReusableMessage) {
             if (logEvent instanceof Log4jLogEvent) {
-                ((Log4jLogEvent) logEvent).makeMessageImmutable();
+                logEvent = logEvent.toImmutable();
             } else if (logEvent instanceof MutableLogEvent) {
                 // MutableLogEvents need to be translated into the RingBuffer 
by the MUTABLE_TRANSLATOR.
                 // That translator calls MutableLogEvent.initFrom to copy the 
event, which will makeMessageImmutable the
                 // message.
                 if (translator != MUTABLE_TRANSLATOR) { // should not happen...
                     // TRANSLATOR expects an immutable LogEvent
-                    logEvent = ((MutableLogEvent) logEvent).createMemento();
+                    logEvent = logEvent.toImmutable();
                 }
             } else { // custom log event, with a ReusableMessage
                 showWarningAboutCustomLogEventWithReusableMessage(logEvent);
@@ -436,7 +436,7 @@ public class AsyncLoggerConfigDisruptor extends 
AbstractLifeCycle implements Asy
             // The original event will be re-used and modified in an 
application thread later,
             // so take a snapshot of it, which can be safely processed in the
             // some-loggers-async background thread.
-            result = ((RingBufferLogEvent) event).createMemento();
+            result = event.toImmutable();
         }
         return result;
     }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
index d3ab5a10e8..71d1b55f5d 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
@@ -18,6 +18,8 @@ package org.apache.logging.log4j.core.async;
 
 import com.lmax.disruptor.EventFactory;
 import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.util.Arrays;
 import java.util.Map;
 import org.apache.logging.log4j.Level;
@@ -83,7 +85,6 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
     private StringBuilder messageText;
     private Object[] parameters;
     private transient Throwable thrown;
-    private ThrowableProxy thrownProxy;
     private StringMap contextData = ContextDataFactory.createContextData();
     private Marker marker;
     private String fqcn;
@@ -117,7 +118,6 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
         initTime(clock);
         this.nanoTime = nanoClock.nanoTime();
         this.thrown = aThrowable;
-        this.thrownProxy = null;
         this.marker = aMarker;
         this.fqcn = theFqcn;
         this.location = aLocation;
@@ -137,7 +137,7 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
 
     @Override
     public LogEvent toImmutable() {
-        return createMemento();
+        return Log4jLogEvent.createMemento(this);
     }
 
     private void setMessage(final Message msg) {
@@ -334,24 +334,12 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
 
     @Override
     public Throwable getThrown() {
-        // after deserialization, thrown is null but thrownProxy may be 
non-null
-        if (thrown == null) {
-            if (thrownProxy != null) {
-                thrown = thrownProxy.getThrowable();
-            }
-        }
         return thrown;
     }
 
     @Override
     public ThrowableProxy getThrownProxy() {
-        // lazily instantiate the (expensive) ThrowableProxy
-        if (thrownProxy == null) {
-            if (thrown != null) {
-                thrownProxy = new ThrowableProxy(thrown);
-            }
-        }
-        return this.thrownProxy;
+        return thrown != null ? new ThrowableProxy(thrown) : null;
     }
 
     @Override
@@ -420,7 +408,6 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
         this.loggerName = null;
         clearMessage();
         this.thrown = null;
-        this.thrownProxy = null;
         clearContextData();
         this.marker = null;
         this.fqcn = null;
@@ -458,26 +445,32 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
         }
     }
 
-    private void writeObject(final java.io.ObjectOutputStream out) throws 
IOException {
-        getThrownProxy(); // initialize the ThrowableProxy before serializing
-        out.defaultWriteObject();
+    private Object writeReplace() throws IOException {
+        return Log4jLogEvent.serialize(this, this.includeLocation);
+    }
+
+    private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        throw new InvalidObjectException("Proxy required");
     }
 
     /**
      * Creates and returns a new immutable copy of this {@code 
RingBufferLogEvent}.
      *
      * @return a new immutable copy of the data in this {@code 
RingBufferLogEvent}
+     * @deprecated since 2.25.0. Use {@link LogEvent#toImmutable()} instead.
      */
+    @Deprecated
     public LogEvent createMemento() {
-        final Log4jLogEvent.Builder builder = new Log4jLogEvent.Builder();
-        initializeBuilder(builder);
-        return builder.build();
+        return toImmutable();
     }
 
     /**
      * Initializes the specified {@code Log4jLogEvent.Builder} from this 
{@code RingBufferLogEvent}.
      * @param builder the builder whose fields to populate
+     *
+     * @deprecated since 2.25.0. Use {@link 
Log4jLogEvent.Builder#Builder(LogEvent)} instead.
      */
+    @Deprecated
     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
         // If the data is not frozen, make a copy of it.
         final StringMap oldContextData = this.contextData;
@@ -503,7 +496,6 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
                 .setThreadName(threadName) //
                 .setThreadPriority(threadPriority) //
                 .setThrown(getThrown()) // may deserialize from thrownProxy
-                .setThrownProxy(thrownProxy) // avoid unnecessarily creating 
thrownProxy
                 .setInstant(instant) //
         ;
     }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
index 9763ff7fce..f6fee9fcf5 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
@@ -33,6 +33,9 @@ import org.apache.logging.log4j.util.StringMap;
  * the ringbuffer {@code RingBufferLogEvent}. After this translator populated
  * the ringbuffer event, the disruptor will update the sequence number so that
  * the event can be consumed by another thread.
+ * <p>
+ *   <strong>Usage note:</strong> This class is only used on the thread that 
created it.
+ * </p>
  */
 public class RingBufferLogEventTranslator implements 
EventTranslator<RingBufferLogEvent> {
 
@@ -45,13 +48,15 @@ public class RingBufferLogEventTranslator implements 
EventTranslator<RingBufferL
     protected Message message;
     protected Throwable thrown;
     private ContextStack contextStack;
-    private long threadId = Thread.currentThread().getId();
-    private String threadName = Thread.currentThread().getName();
-    private int threadPriority = Thread.currentThread().getPriority();
     private StackTraceElement location;
     private Clock clock;
     private NanoClock nanoClock;
 
+    // Due to the usage pattern of this class, these are effectively final
+    private long threadId = Thread.currentThread().getId();
+    private String threadName = Thread.currentThread().getName();
+    private int threadPriority = Thread.currentThread().getPriority();
+
     // @Override
     @Override
     public void translateTo(final RingBufferLogEvent event, final long 
sequence) {
@@ -124,6 +129,11 @@ public class RingBufferLogEventTranslator implements 
EventTranslator<RingBufferL
         this.nanoClock = aNanoClock;
     }
 
+    /**
+     * @deprecated since 2.25.0. {@link RingBufferLogEventTranslator} 
instances should only be used on the thread that
+     * created it.
+     */
+    @Deprecated
     public void updateThreadValues() {
         final Thread currentThread = Thread.currentThread();
         this.threadId = currentThread.getId();
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
index 309e4c78c5..7cad00404b 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
@@ -44,7 +44,6 @@ import 
org.apache.logging.log4j.core.config.properties.PropertiesConfiguration;
 import org.apache.logging.log4j.core.filter.AbstractFilterable;
 import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
 import org.apache.logging.log4j.core.impl.LocationAware;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.impl.LogEventFactory;
 import org.apache.logging.log4j.core.impl.ReusableLogEventFactory;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
@@ -611,14 +610,6 @@ public class LoggerConfig extends AbstractFilterable 
implements LocationAware {
             final Throwable t,
             final List<Property> props) {
         final List<Property> results = new ArrayList<>(props.size());
-        final LogEvent event = Log4jLogEvent.newBuilder()
-                .setMessage(data)
-                .setMarker(marker)
-                .setLevel(level)
-                .setLoggerName(loggerName)
-                .setLoggerFqcn(fqcn)
-                .setThrown(t)
-                .build();
         for (int i = 0; i < props.size(); i++) {
             final Property prop = props.get(i);
             final String value = prop.evaluate(config.getStrSubstitutor()); // 
since LOG4J2-1575
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
index 7185bc7bc8..e47fa88049 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
@@ -29,7 +29,7 @@ import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.async.RingBufferLogEvent;
+import org.apache.logging.log4j.core.async.InternalAsyncUtil;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.time.Instant;
@@ -62,96 +62,116 @@ public class Log4jLogEvent implements LogEvent {
     private static volatile NanoClock nanoClock = new DummyNanoClock();
     private static final ContextDataInjector CONTEXT_DATA_INJECTOR = 
ContextDataInjectorFactory.createInjector();
 
+    // 1. Fields with an immutable type, initialized in the constructor
     private final String loggerFqcn;
-    private final Marker marker;
     private final Level level;
     private final String loggerName;
-    private Message message;
-    private final MutableInstant instant = new MutableInstant();
+    private final Marker marker;
     private final transient Throwable thrown;
-    private ThrowableProxy thrownProxy;
-    private final StringMap contextData;
-    private final ThreadContext.ContextStack contextStack;
-    private long threadId;
-    private String threadName;
-    private int threadPriority;
-    private StackTraceElement source;
-    private boolean includeLocation;
-    private boolean endOfBatch = false;
     /** @since Log4J 2.4 */
     private final transient long nanoTime;
+    // This field is mutable, but its state is not shared with other objects.
+    private final MutableInstant instant = new MutableInstant();
+
+    // 2. Fields with setters, initialized in the constructor.
+    private boolean endOfBatch;
+    private boolean includeLocation;
+
+    // 3. Fields with an immutable type, initialized lazily.
+    //    These fields self-initialize if not provided.
+    private StackTraceElement source;
+    private String threadName;
+    private long threadId;
+    private int threadPriority;
+
+    // 4. Fields with a potentially mutable type.
+    //    These fields can cause mutability problems for Log4jLogEvent.
+    private Message message;
+    private final StringMap contextData;
+    private final ThreadContext.ContextStack contextStack;
+
+    // 5. Deprecated fields, only used for serialization
+    private ThrowableProxy thrownProxy;
 
     /** LogEvent Builder helper class. */
     public static class Builder implements 
org.apache.logging.log4j.core.util.Builder<LogEvent> {
 
+        // 1. Fields with an immutable type, initialized eagerly.
+        //    These fields always keep the value assigned.
         private String loggerFqcn;
-        private Marker marker;
         private Level level;
         private String loggerName;
-        private Message message;
+        private Marker marker;
         private Throwable thrown;
+        private boolean endOfBatch;
+        private boolean includeLocation;
+        private long nanoTime;
+        // This field is mutable, but it is always copied.
         private final MutableInstant instant = new MutableInstant();
-        private ThrowableProxy thrownProxy;
-        private StringMap contextData = createContextData((List<Property>) 
null);
-        private ThreadContext.ContextStack contextStack = 
ThreadContext.getImmutableStack();
-        private long threadId;
+
+        // 2. Fields with an immutable type, initialized lazily.
+        //    These fields self-initialize if not provided.
+        private StackTraceElement source;
         private String threadName;
+        private long threadId;
         private int threadPriority;
-        private StackTraceElement source;
-        private boolean includeLocation;
-        private boolean endOfBatch = false;
-        private long nanoTime;
 
-        public Builder() {}
+        // 3. Fields with a mutable type.
+        //    These fields require special handling.
+        private Message message;
+        private StringMap contextData;
+        private ThreadContext.ContextStack contextStack;
 
+        public Builder() {
+            this.contextData = createContextData((List<Property>) null);
+            this.contextStack = ThreadContext.getImmutableStack();
+        }
+
+        /**
+         * Initializes the builder with an <strong>immutable</strong> instance 
or a copy of the log event fields.
+         *
+         * @param other The log event to copy.
+         */
         public Builder(final LogEvent other) {
             Objects.requireNonNull(other);
-            if (other instanceof RingBufferLogEvent) {
-                ((RingBufferLogEvent) other).initializeBuilder(this);
-                return;
-            }
-            if (other instanceof MutableLogEvent) {
-                ((MutableLogEvent) other).initializeBuilder(this);
-                return;
-            }
+            // These can be safely copied, since the getters have no side 
effects.
             this.loggerFqcn = other.getLoggerFqcn();
-            this.marker = other.getMarker();
             this.level = other.getLevel();
             this.loggerName = other.getLoggerName();
-            this.message = other.getMessage();
-            this.instant.initFrom(other.getInstant());
+            this.marker = other.getMarker();
             this.thrown = other.getThrown();
-            this.contextStack = other.getContextStack();
-            this.includeLocation = other.isIncludeLocation();
             this.endOfBatch = other.isEndOfBatch();
+            this.includeLocation = other.isIncludeLocation();
             this.nanoTime = other.getNanoTime();
+            this.instant.initFrom(other.getInstant());
 
-            // Avoid unnecessarily initializing thrownProxy, threadName and 
source if possible
-            if (other instanceof Log4jLogEvent) {
-                final Log4jLogEvent evt = (Log4jLogEvent) other;
-                this.contextData = evt.contextData;
-                this.thrownProxy = evt.thrownProxy;
-                this.source = evt.source;
-                this.threadId = evt.threadId;
-                this.threadName = evt.threadName;
-                this.threadPriority = evt.threadPriority;
-            } else {
-                if (other.getContextData() instanceof StringMap) {
-                    this.contextData = (StringMap) other.getContextData();
-                } else {
-                    if (this.contextData.isFrozen()) {
-                        this.contextData = 
ContextDataFactory.createContextData();
-                    } else {
-                        this.contextData.clear();
-                    }
-                    this.contextData.putAll(other.getContextData());
-                }
-                this.thrownProxy = other.getThrownProxy();
-                this.source = other.getSource();
-                this.threadId = other.getThreadId();
-                this.threadName = other.getThreadName();
-                this.threadPriority = other.getThreadPriority();
-            }
+            // These getters are:
+            // * side-effect-free in RingBufferLogEvent and MutableLogEvent,
+            // * have side effects in Log4jLogEvent,
+            //   but since we are copying the event, we want to call them.
+            this.threadId = other.getThreadId();
+            this.threadPriority = other.getThreadPriority();
+            this.threadName = other.getThreadName();
+            // The `getSource()` method is:
+            // * side-effect-free in RingBufferLogEvent,
+            // * have side effects in Log4jLogEvent and MutableLogEvent,
+            //   but since we are copying the event, we want to call it.
+            this.source = other.getSource();
+
+            Message message = other.getMessage();
+            this.message = message instanceof ReusableMessage
+                    ? ((ReusableMessage) message).memento()
+                    : InternalAsyncUtil.makeMessageImmutable(message);
+
+            ReadOnlyStringMap contextData = other.getContextData();
+            this.contextData = contextData instanceof StringMap && 
((StringMap) contextData).isFrozen()
+                    ? (StringMap) contextData
+                    : contextData != null
+                            ? ContextDataFactory.createContextData(contextData)
+                            : ContextDataFactory.emptyFrozenContextData();
+
+            // TODO: The immutability of the context stack is not checked.
+            this.contextStack = other.getContextStack();
         }
 
         public Builder setLevel(final Level level) {
@@ -194,8 +214,11 @@ public class Log4jLogEvent implements LogEvent {
             return this;
         }
 
+        /**
+         * @deprecated since 2.25.0 without a replacement
+         */
+        @Deprecated
         public Builder setThrownProxy(final ThrowableProxy thrownProxy) {
-            this.thrownProxy = thrownProxy;
             return this;
         }
 
@@ -271,7 +294,6 @@ public class Log4jLogEvent implements LogEvent {
                     level,
                     message,
                     thrown,
-                    thrownProxy,
                     contextData,
                     contextStack,
                     threadId,
@@ -311,7 +333,6 @@ public class Log4jLogEvent implements LogEvent {
                 (Throwable) null,
                 null,
                 null,
-                null,
                 0,
                 null,
                 0,
@@ -332,7 +353,6 @@ public class Log4jLogEvent implements LogEvent {
                 Strings.EMPTY,
                 null,
                 null,
-                (Throwable) null,
                 null,
                 null,
                 null,
@@ -392,7 +412,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 t,
-                null,
                 createContextData(properties),
                 ThreadContext.getDepth() == 0 ? null : 
ThreadContext.cloneStack(), // mutable copy
                 0, // thread id
@@ -430,7 +449,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 t,
-                null,
                 createContextData(properties),
                 ThreadContext.getDepth() == 0 ? null : 
ThreadContext.cloneStack(), // mutable copy
                 0, // thread id
@@ -476,7 +494,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 t,
-                null,
                 createContextData(mdc),
                 ndc,
                 0,
@@ -496,7 +513,7 @@ public class Log4jLogEvent implements LogEvent {
      * @param level The logging Level.
      * @param message The Message.
      * @param thrown A Throwable or null.
-     * @param thrownProxy A ThrowableProxy or null.
+     * @param ignoredThrownProxy Ignored.
      * @param mdc The mapped diagnostic context.
      * @param ndc the nested diagnostic context.
      * @param threadName The name of the thread.
@@ -513,7 +530,7 @@ public class Log4jLogEvent implements LogEvent {
             final Level level,
             final Message message,
             final Throwable thrown,
-            final ThrowableProxy thrownProxy,
+            final ThrowableProxy ignoredThrownProxy,
             final Map<String, String> mdc,
             final ThreadContext.ContextStack ndc,
             final String threadName,
@@ -526,7 +543,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 thrown,
-                thrownProxy,
                 createContextData(mdc),
                 ndc,
                 0,
@@ -547,7 +563,6 @@ public class Log4jLogEvent implements LogEvent {
      * @param level The logging Level.
      * @param message The Message.
      * @param thrown A Throwable or null.
-     * @param thrownProxy A ThrowableProxy or null.
      * @param contextData The key-value pairs from the context.
      * @param contextStack the nested diagnostic context.
      * @param threadId the thread ID
@@ -566,7 +581,6 @@ public class Log4jLogEvent implements LogEvent {
             final Level level,
             final Message message,
             final Throwable thrown,
-            final ThrowableProxy thrownProxy,
             final StringMap contextData,
             final ThreadContext.ContextStack contextStack,
             final long threadId,
@@ -583,7 +597,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 thrown,
-                thrownProxy,
                 contextData,
                 contextStack,
                 threadId,
@@ -603,7 +616,6 @@ public class Log4jLogEvent implements LogEvent {
             final Level level,
             final Message message,
             final Throwable thrown,
-            final ThrowableProxy thrownProxy,
             final StringMap contextData,
             final ThreadContext.ContextStack contextStack,
             final long threadId,
@@ -619,7 +631,6 @@ public class Log4jLogEvent implements LogEvent {
                 level,
                 message,
                 thrown,
-                thrownProxy,
                 contextData,
                 contextStack,
                 threadId,
@@ -641,7 +652,6 @@ public class Log4jLogEvent implements LogEvent {
             final Level level,
             final Message message,
             final Throwable thrown,
-            final ThrowableProxy thrownProxy,
             final StringMap contextData,
             final ThreadContext.ContextStack contextStack,
             final long threadId,
@@ -655,7 +665,6 @@ public class Log4jLogEvent implements LogEvent {
         this.level = level == null ? Level.OFF : level; // LOG4J2-462, 
LOG4J2-465
         this.message = message;
         this.thrown = thrown;
-        this.thrownProxy = thrownProxy;
         this.contextData = contextData == null ? 
ContextDataFactory.createContextData() : contextData;
         this.contextStack = contextStack == null ? ThreadContext.EMPTY_STACK : 
contextStack;
         this.threadId = threadId;
@@ -720,9 +729,17 @@ public class Log4jLogEvent implements LogEvent {
         if (getMessage() instanceof ReusableMessage) {
             makeMessageImmutable();
         }
+        populateLazilyInitializedFields();
         return this;
     }
 
+    private void populateLazilyInitializedFields() {
+        getSource();
+        getThreadId();
+        getThreadPriority();
+        getThreadName();
+    }
+
     /**
      * Returns the logging Level.
      * @return the Level associated with this event.
@@ -751,7 +768,9 @@ public class Log4jLogEvent implements LogEvent {
     }
 
     public void makeMessageImmutable() {
-        message = new MementoMessage(message.getFormattedMessage(), 
message.getFormat(), message.getParameters());
+        message = message instanceof ReusableMessage
+                ? ((ReusableMessage) message).memento()
+                : InternalAsyncUtil.makeMessageImmutable(message);
     }
 
     @Override
@@ -814,10 +833,8 @@ public class Log4jLogEvent implements LogEvent {
      */
     @Override
     public ThrowableProxy getThrownProxy() {
-        if (thrownProxy == null && thrown != null) {
-            thrownProxy = new ThrowableProxy(thrown);
-        }
-        return thrownProxy;
+        // The `thrownProxy` field is non-null only after deserialization.
+        return thrownProxy != null ? thrownProxy : thrown != null ? new 
ThrowableProxy(thrown) : null;
     }
 
     /**
@@ -912,7 +929,6 @@ public class Log4jLogEvent implements LogEvent {
      * @return a LogEventProxy.
      */
     protected Object writeReplace() {
-        getThrownProxy(); // ensure ThrowableProxy is initialized
         return new LogEventProxy(this, this.includeLocation);
     }
 
@@ -927,7 +943,6 @@ public class Log4jLogEvent implements LogEvent {
      */
     public static Serializable serialize(final LogEvent event, final boolean 
includeLocation) {
         if (event instanceof Log4jLogEvent) {
-            event.getThrownProxy(); // ensure ThrowableProxy is initialized
             return new LogEventProxy((Log4jLogEvent) event, includeLocation);
         }
         return new LogEventProxy(event, includeLocation);
@@ -943,7 +958,6 @@ public class Log4jLogEvent implements LogEvent {
      * @see #serialize(LogEvent, boolean)
      */
     public static Serializable serialize(final Log4jLogEvent event, final 
boolean includeLocation) {
-        event.getThrownProxy(); // ensure ThrowableProxy is initialized
         return new LogEventProxy(event, includeLocation);
     }
 
@@ -962,7 +976,6 @@ public class Log4jLogEvent implements LogEvent {
                     proxy.level,
                     proxy.message,
                     proxy.thrown,
-                    proxy.thrownProxy,
                     proxy.contextData,
                     proxy.contextStack,
                     proxy.threadId,
@@ -983,6 +996,12 @@ public class Log4jLogEvent implements LogEvent {
         throw new InvalidObjectException("Proxy required");
     }
 
+    /**
+     * Creates a new immutable copy of a {@link LogEvent}.
+     *
+     * @param logEvent The log event to copy.
+     * @return An immutable log event.
+     */
     public static LogEvent createMemento(final LogEvent logEvent) {
         return new Log4jLogEvent.Builder(logEvent).build();
     }
@@ -993,7 +1012,14 @@ public class Log4jLogEvent implements LogEvent {
      * @return a new immutable copy of the data in this {@code Log4jLogEvent}
      */
     public static Log4jLogEvent createMemento(final LogEvent event, final 
boolean includeLocation) {
-        return deserialize(serialize(event, includeLocation));
+        // In the case `includeLocation` is false, we temporarily disable its 
computation.
+        if (event.isIncludeLocation() && !includeLocation) {
+            event.setIncludeLocation(false);
+            Log4jLogEvent memento = (Log4jLogEvent) createMemento(event);
+            event.setIncludeLocation(true);
+            return memento;
+        }
+        return (Log4jLogEvent) createMemento(event);
     }
 
     @Override
@@ -1065,9 +1091,6 @@ public class Log4jLogEvent implements LogEvent {
         if (thrown != null ? !thrown.equals(that.thrown) : that.thrown != 
null) {
             return false;
         }
-        if (thrownProxy != null ? !thrownProxy.equals(that.thrownProxy) : 
that.thrownProxy != null) {
-            return false;
-        }
 
         return true;
     }
@@ -1083,7 +1106,6 @@ public class Log4jLogEvent implements LogEvent {
         result = 31 * result + instant.hashCode();
         result = 31 * result + (int) (nanoTime ^ (nanoTime >>> 32));
         result = 31 * result + (thrown != null ? thrown.hashCode() : 0);
-        result = 31 * result + (thrownProxy != null ? thrownProxy.hashCode() : 
0);
         result = 31 * result + (contextData != null ? contextData.hashCode() : 
0);
         result = 31 * result + (contextStack != null ? contextStack.hashCode() 
: 0);
         result = 31 * result + (int) (threadId ^ (threadId >>> 32));
@@ -1146,7 +1168,7 @@ public class Log4jLogEvent implements LogEvent {
             this.timeMillis = event.instant.getEpochMillisecond();
             this.nanoOfMillisecond = event.instant.getNanoOfMillisecond();
             this.thrown = event.thrown;
-            this.thrownProxy = event.thrownProxy;
+            this.thrownProxy = event.getThrownProxy();
             this.contextData = event.contextData;
             this.contextStack = event.contextStack;
             this.source = includeLocation ? event.getSource() : event.source;
@@ -1172,9 +1194,14 @@ public class Log4jLogEvent implements LogEvent {
             this.thrownProxy = event.getThrownProxy();
             this.contextData = memento(event.getContextData());
             this.contextStack = event.getContextStack();
-            this.source = includeLocation
-                    ? event.getSource()
-                    : event instanceof MutableLogEvent ? ((MutableLogEvent) 
event).source : null;
+            // In the case `includeLocation` is false, we temporarily disable 
its computation.
+            if (event.isIncludeLocation() && !includeLocation) {
+                event.setIncludeLocation(false);
+                this.source = event.getSource();
+                event.setIncludeLocation(true);
+            } else {
+                this.source = event.getSource();
+            }
             this.threadId = event.getThreadId();
             this.threadName = event.getThreadName();
             this.threadPriority = event.getThreadPriority();
@@ -1218,8 +1245,8 @@ public class Log4jLogEvent implements LogEvent {
                     loggerFQCN,
                     level,
                     message(),
-                    thrown,
-                    thrownProxy,
+                    // `thrown` is always null after deserialization
+                    thrownProxy != null ? thrownProxy.getThrowable() : null,
                     contextData,
                     contextStack,
                     threadId,
@@ -1231,6 +1258,7 @@ public class Log4jLogEvent implements LogEvent {
                     nanoTime);
             result.setEndOfBatch(isEndOfBatch);
             result.setIncludeLocation(isLocationRequired);
+            result.thrownProxy = thrownProxy;
             return result;
         }
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 10fe0345ef..151f4d4bfa 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -64,7 +64,6 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
     private StringBuilder messageText;
     private Object[] parameters;
     private Throwable thrown;
-    private ThrowableProxy thrownProxy;
     private StringMap contextData = ContextDataFactory.createContextData();
     private Marker marker;
     private String loggerFqcn;
@@ -82,9 +81,15 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
         this.parameters = replacementParameters;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   If {@link #isIncludeLocation()} is true, caller information for this 
instance will also be computed.
+     * </p>
+     */
     @Override
     public Log4jLogEvent toImmutable() {
-        return createMemento();
+        return (Log4jLogEvent) Log4jLogEvent.createMemento(this);
     }
 
     /**
@@ -94,6 +99,14 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
      * <p>
      * This method is used on async logger ringbuffer slots holding 
MutableLogEvent objects in each slot.
      * </p>
+     * <p>
+     *   <strong>Warning:</strong> If {@code event.getMessage()} is an 
instance of {@link ReusableMessage}, this method
+     *   remove the parameter references from the original message. Callers 
should:
+     * </p>
+     * <ol>
+     *   <li>Either make sure that the {@code event} will not be used 
again.</li>
+     *   <li>Or call {@link LogEvent#toImmutable()} before calling this 
method.</li>
+     * </ol>
      *
      * @param event the event to copy data from
      */
@@ -103,7 +116,6 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
         this.level = event.getLevel();
         this.loggerName = event.getLoggerName();
         this.thrown = event.getThrown();
-        this.thrownProxy = event.getThrownProxy();
 
         this.instant.initFrom(event.getInstant());
 
@@ -134,7 +146,6 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
         message = null;
         messageFormat = null;
         thrown = null;
-        thrownProxy = null;
         source = null;
         if (contextData != null) {
             if (contextData.isFrozen()) { // came from CopyOnWrite thread 
context
@@ -212,6 +223,26 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
         return message;
     }
 
+    /**
+     * Sets the log message of the event.
+     *
+     * <p>
+     *   <strong>Warning:</strong> This method <strong>mutates</strong> the 
state of the {@code message}
+     *   parameter:
+     * </p>
+     * <ol>
+     *   <li>
+     *     If the message is a {@link 
org.apache.logging.log4j.message.ReusableMessage}, this method will remove its
+     *     parameter references, which prevents it from being used again.
+     *   </li>
+     *   <li>
+     *     Otherwise the lazy {@link Message#getFormattedMessage()} message 
might be called.
+     *     See <a 
href="https://logging.apache.org/log4j/2.x/manual/systemproperties.html#log4j2.formatMsgAsync";>{@code
 log4j2.formatMsgAsync}</a>
+     *     for details.
+     *   </li>
+     * </ol>
+     * @param msg The log message. The object passed will be 
<strong>modified</strong> by this method and should not be reused.
+     */
     public void setMessage(final Message msg) {
         if (msg instanceof ReusableMessage) {
             final ReusableMessage reusable = (ReusableMessage) msg;
@@ -349,10 +380,7 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
      */
     @Override
     public ThrowableProxy getThrownProxy() {
-        if (thrownProxy == null && thrown != null) {
-            thrownProxy = new ThrowableProxy(thrown);
-        }
-        return thrownProxy;
+        return thrown != null ? new ThrowableProxy(thrown) : null;
     }
 
     public void setSource(StackTraceElement source) {
@@ -461,7 +489,7 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
      * @return a LogEventProxy.
      */
     protected Object writeReplace() {
-        return new Log4jLogEvent.LogEventProxy(this, this.includeLocation);
+        return Log4jLogEvent.serialize(this, this.includeLocation);
     }
 
     private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
@@ -473,15 +501,20 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
      * If {@link #isIncludeLocation()} is true, this will obtain caller 
location information.
      *
      * @return a new immutable copy of the data in this {@code MutableLogEvent}
+     * @deprecated since 2.25.0. Use {@link LogEvent#toImmutable()} instead.
      */
+    @Deprecated
     public Log4jLogEvent createMemento() {
-        return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, 
includeLocation));
+        return toImmutable();
     }
 
     /**
      * Initializes the specified {@code Log4jLogEvent.Builder} from this 
{@code MutableLogEvent}.
      * @param builder the builder whose fields to populate
+     *
+     * @deprecated since 2.25.0. Use {@link 
Log4jLogEvent.Builder#Builder(LogEvent)} instead.
      */
+    @Deprecated
     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
         builder.setContextData(contextData) //
                 .setContextStack(contextStack) //
@@ -498,7 +531,6 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
                 .setThreadName(threadName) //
                 .setThreadPriority(threadPriority) //
                 .setThrown(getThrown()) // may deserialize from thrownProxy
-                .setThrownProxy(thrownProxy) // avoid unnecessarily creating 
thrownProxy
                 .setInstant(instant) //
         ;
     }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index e954d9b7f7..7b5d23ab8a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -66,13 +66,27 @@ public class ReusableLogEventFactory implements 
LogEventFactory, LocationAwareLo
 
     /**
      * Creates a log event.
-     *
+     * <p>
+     *   <strong>Implementation note:</strong> This method 
<strong>mutates</strong> the state of the {@code message}
+     *   parameter:
+     * </p>
+     * <ol>
+     *   <li>
+     *     If the message is a {@link 
org.apache.logging.log4j.message.ReusableMessage}, this method will remove its
+     *     parameter references, which prevents it from being used again.
+     *   </li>
+     *   <li>
+     *     Otherwise the lazy {@link Message#getFormattedMessage()} message 
might be called.
+     *     See <a 
href="https://logging.apache.org/log4j/2.x/manual/systemproperties.html#log4j2.formatMsgAsync";>{@code
 log4j2.formatMsgAsync}</a>
+     *     for details.
+     *   </li>
+     * </ol>
      * @param loggerName The name of the Logger.
      * @param marker An optional Marker.
      * @param fqcn The fully qualified class name of the caller.
      * @param location The location of the caller.
      * @param level The event Level.
-     * @param message The Message.
+     * @param message The log message. The object passed will be 
<strong>modified</strong> by this method and should not be reused.
      * @param properties Properties to be added to the log event.
      * @param t An optional Throwable.
      * @return The LogEvent.
@@ -105,8 +119,11 @@ public class ReusableLogEventFactory implements 
LogEventFactory, LocationAwareLo
                 ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : 
ThreadContext.cloneStack()); // mutable copy
 
         if (THREAD_NAME_CACHING_STRATEGY == 
ThreadNameCachingStrategy.UNCACHED) {
-            result.setThreadName(Thread.currentThread().getName()); // 
Thread.getName() allocates Objects on each call
-            result.setThreadPriority(Thread.currentThread().getPriority());
+            Thread currentThread = Thread.currentThread();
+            result.setThreadId(currentThread.getId());
+            // Before JRE 8u102, Thread.getName() allocated objects on each 
call
+            result.setThreadName(currentThread.getName());
+            result.setThreadPriority(currentThread.getPriority());
         }
         return result;
     }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java
index b05ceb56bc..4de5d210b8 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java
@@ -36,7 +36,6 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.impl.ThrowableProxy;
 import org.apache.logging.log4j.core.jackson.XmlConstants;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
@@ -336,8 +335,8 @@ abstract class AbstractJacksonLayout extends 
AbstractStringLayout {
     private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
         // TODO Jackson-based layouts have certain filters set up for 
Log4jLogEvent.
         // TODO Need to set up the same filters for MutableLogEvent but don't 
know how...
-        // This is a workaround.
-        return event instanceof Log4jLogEvent ? event : 
Log4jLogEvent.createMemento(event);
+        // This is a workaround, since `toImmutable()` currently returns a 
Log4jLogEvent.
+        return event.toImmutable();
     }
 
     protected Object wrapLogEvent(final LogEvent event) {
diff --git 
a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/SmtpManagerTest.java
 
b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/SmtpManagerTest.java
index 84c55e227d..9db730ddf9 100644
--- 
a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/SmtpManagerTest.java
+++ 
b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/SmtpManagerTest.java
@@ -16,15 +16,12 @@
  */
 package org.apache.logging.log4j.smtp;
 
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.SmtpAppender;
 import org.apache.logging.log4j.core.async.RingBufferLogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
-import org.apache.logging.log4j.core.impl.MementoMessage;
 import org.apache.logging.log4j.core.impl.MutableLogEvent;
 import org.apache.logging.log4j.core.net.MailManager;
 import org.apache.logging.log4j.core.util.ClockFactory;
@@ -54,17 +51,14 @@ class SmtpManagerTest {
                 .setBufferSize(10)
                 .build();
         final MailManager mailManager = appender.getManager();
-        assertThat("is instance of SmtpManager", mailManager instanceof 
SmtpManager);
+        assertThat(mailManager).isInstanceOf(SmtpManager.class);
         final SmtpManager smtpManager = (SmtpManager) mailManager;
         smtpManager.removeAllBufferedEvents(); // in case this smtpManager is 
reused
         smtpManager.add(event);
 
         final LogEvent[] bufferedEvents = 
smtpManager.removeAllBufferedEvents();
-        assertThat("unexpected number of buffered events", 
bufferedEvents.length, is(1));
-        assertThat(
-                "expected the immutable version of the event to be buffered",
-                bufferedEvents[0].getMessage(),
-                is(instanceOf(MementoMessage.class)));
+        assertThat(bufferedEvents).as("Buffered events").hasSize(1);
+        assertThat(bufferedEvents[0].getMessage()).as("Immutable 
message").isNotInstanceOf(ReusableMessage.class);
     }
 
     // LOG4J2-3172: make sure existing protections are not violated
diff --git a/src/changelog/.2.x.x/throwable-proxy-clean-up.xml 
b/src/changelog/.2.x.x/throwable-proxy-clean-up.xml
new file mode 100644
index 0000000000..acc6378895
--- /dev/null
+++ b/src/changelog/.2.x.x/throwable-proxy-clean-up.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="changed">
+  <description format="asciidoc">
+    Improve implementations of `LogEvent.toImmutable()` and 
`ReusableMessage.memento()` and remove usage of `ThrowableProxy`.
+  </description>
+</entry>

Reply via email to