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

mattsicker pushed a commit to branch feature/3.x/merge-log-event-improvements
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit bff263e3d660299390804584a4585f87ecba5aff
Author: Matt Sicker <[email protected]>
AuthorDate: Thu Jul 31 16:08:16 2025 -0500

    Synchronize Log4jLogEvent-related changes from 2.x
    
    This updates `Log4jLogEvent` and its builder class to work similarly to
    what is in 2.25.0. The previously added `MementoLogEvent` class can be
    removed as the builder can make memento copies better.
---
 .../log4j/async/logger/RingBufferLogEvent.java     |  35 ---
 .../logging/log4j/core/impl/Log4jLogEventTest.java |   8 +-
 .../org/apache/logging/log4j/core/LogEvent.java    |  15 +-
 .../logging/log4j/core/ReusableLogEvent.java       |   7 -
 .../log4j/core/async/InternalAsyncUtil.java        |   4 +-
 .../logging/log4j/core/impl/Log4jLogEvent.java     | 221 +++++++++--------
 .../logging/log4j/core/impl/MementoLogEvent.java   | 262 ---------------------
 .../logging/log4j/core/impl/MutableLogEvent.java   |  50 ++--
 .../log4j/core/impl/ReusableLogEventFactory.java   |  22 +-
 9 files changed, 175 insertions(+), 449 deletions(-)

diff --git 
a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/RingBufferLogEvent.java
 
b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/RingBufferLogEvent.java
index c7fbbd3255..abd6e5764f 100644
--- 
a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/RingBufferLogEvent.java
+++ 
b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/RingBufferLogEvent.java
@@ -25,7 +25,6 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.ReusableLogEvent;
 import org.apache.logging.log4j.core.async.InternalAsyncUtil;
 import org.apache.logging.log4j.core.impl.ContextDataFactory;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.impl.MementoMessage;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.Instant;
@@ -495,38 +494,4 @@ public class RingBufferLogEvent implements 
ReusableLogEvent, ReusableMessage, Ch
             }
         }
     }
-
-    /**
-     * Initializes the specified {@code Log4jLogEvent.Builder} from this 
{@code RingBufferLogEvent}.
-     * @param builder the builder whose fields to populate
-     */
-    @Override
-    public void initializeBuilder(final Log4jLogEvent.Builder builder) {
-        // If the data is not frozen, make a copy of it.
-        // TODO: merge with MementoLogEvent#memento
-        final StringMap oldContextData = this.contextData;
-        final StringMap contextData;
-        if (oldContextData != null && !oldContextData.isFrozen()) {
-            contextData = ContextDataFactory.createContextData(oldContextData);
-        } else {
-            contextData = oldContextData;
-        }
-        builder.setContextData(contextData) //
-                .setContextStack(contextStack) //
-                .setEndOfBatch(endOfBatch) //
-                .setIncludeLocation(includeLocation) //
-                .setLevel(getLevel()) // ensure non-null
-                .setLoggerFqcn(fqcn) //
-                .setLoggerName(loggerName) //
-                .setMarker(marker) //
-                .setMessage(memento()) // ensure non-null & immutable
-                .setNanoTime(nanoTime) //
-                .setSource(location) //
-                .setThreadId(threadId) //
-                .setThreadName(threadName) //
-                .setThreadPriority(threadPriority) //
-                .setThrown(getThrown()) //
-                .setInstant(instant) //
-        ;
-    }
 }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
index d04c87dc23..725732f46a 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
@@ -16,10 +16,10 @@
  */
 package org.apache.logging.log4j.core.impl;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -326,9 +326,7 @@ public class Log4jLogEventTest {
         different("null fqcn", builder(event).setLoggerFqcn(null), event);
 
         different("different name", builder(event).setLoggerName("different"), 
event);
-        assertThrows(
-                NullPointerException.class,
-                () -> different("null name", 
builder(event).setLoggerName(null), event));
+        different("null name", builder(event).setLoggerName(null), event);
 
         different("different marker", 
builder(event).setMarker(MarkerManager.getMarker("different")), event);
         different("null marker", builder(event).setMarker(null), event);
@@ -365,6 +363,6 @@ public class Log4jLogEventTest {
     @Test
     public void testToString() {
         // Throws an NPE in 2.6.2
-        assertNotNull(Log4jLogEvent.newBuilder().build().toString());
+        assertDoesNotThrow(() -> new Log4jLogEvent().toString());
     }
 }
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 b7ec075be9..064177b2ad 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
@@ -19,9 +19,10 @@ package org.apache.logging.log4j.core;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.core.impl.MementoLogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ReusableMessage;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.jspecify.annotations.Nullable;
 
@@ -40,6 +41,7 @@ public interface LogEvent {
      * 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();
 
@@ -48,9 +50,18 @@ public interface LogEvent {
      * <p>
      *     Location information for both events might be computed by this 
method.
      * </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>
+     * @since 3.0.0
      */
     default LogEvent toMemento() {
-        return new MementoLogEvent(this);
+        return new Log4jLogEvent.Builder(this).build();
     }
 
     /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java
index 12e591aba0..0226ea792a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java
@@ -19,7 +19,6 @@ package org.apache.logging.log4j.core;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.util.StringMap;
@@ -62,10 +61,4 @@ public interface ReusableLogEvent extends LogEvent {
     void setThreadPriority(final int threadPriority);
 
     void setNanoTime(final long nanoTime);
-
-    /**
-     * Initializes the specified {@code Log4jLogEvent.Builder} from this 
{@code ReusableLogEvent}.
-     * @param builder the builder whose fields to populate
-     */
-    void initializeBuilder(final Log4jLogEvent.Builder builder);
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java
index 795424bd93..e735547693 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java
@@ -22,6 +22,7 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.message.AsynchronouslyFormattable;
 import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.InternalApi;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.jspecify.annotations.Nullable;
 
@@ -31,13 +32,14 @@ import org.jspecify.annotations.Nullable;
  *     Consider this class private.
  * </p>
  */
+@InternalApi
 public final class InternalAsyncUtil {
 
     private InternalAsyncUtil() {}
 
     /**
      * Returns the specified message, with its content frozen unless system 
property
-     * {@code log4j.format.msg.async} is true or the message class is 
annotated with
+     * {@code log4j.async.formatMessagesInBackground} is true or the message 
class is annotated with
      * {@link AsynchronouslyFormattable}.
      *
      * @param msg the message object to inspect, modify and return
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 806826f8ab..16078728bb 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
@@ -22,7 +22,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.ReusableLogEvent;
+import org.apache.logging.log4j.core.async.InternalAsyncUtil;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.ClockFactory;
@@ -43,44 +43,65 @@ import org.jspecify.annotations.Nullable;
  */
 public class Log4jLogEvent implements LogEvent {
 
-    private final Marker marker;
+    // 1. Fields with an immutable type, initialized in the constructor
+    // Location data
+    private final String loggerFqcn;
     private final Level level;
     private final String loggerName;
-    private Message message;
-    private final MutableInstant instant = new MutableInstant();
+    private final Marker marker;
     private final Throwable thrown;
-    private final StringMap contextData;
-    private final ThreadContext.ContextStack contextStack;
-    private long threadId;
-    private String threadName;
-    private int threadPriority;
-    private boolean endOfBatch = false;
     /** @since Log4J 2.4 */
     private final long nanoTime;
-    // Location data
-    private final String loggerFqcn;
-    private @Nullable StackTraceElement source;
+    // 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 @Nullable 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;
+
     /** LogEvent Builder helper class. */
     public static class Builder implements 
org.apache.logging.log4j.plugins.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 StringMap contextData;
-        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;
+
+        // 3. Fields with a mutable type.
+        //    These fields require special handling.
+        private Message message;
+        private StringMap contextData;
+        private ThreadContext.ContextStack contextStack;
+
+        // 4. Fields with dependency-injected values.
         private Clock clock;
         private ContextDataInjector contextDataInjector;
 
@@ -88,49 +109,51 @@ public class Log4jLogEvent implements LogEvent {
             initDefaultContextData();
         }
 
+        /**
+         * 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 ReusableLogEvent) {
-                ((ReusableLogEvent) 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());
 
-            initDefaultContextData();
-            // Avoid unnecessarily initializing thrownProxy, threadName and 
source if possible
-            if (other instanceof Log4jLogEvent) {
-                final Log4jLogEvent evt = (Log4jLogEvent) other;
-                this.contextData = evt.contextData;
-                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.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) {
@@ -260,8 +283,8 @@ public class Log4jLogEvent implements LogEvent {
 
         private void initTimeFields() {
             if (instant.getEpochMillisecond() == 0) {
-                if (message instanceof TimestampMessage) {
-                    instant.initFromEpochMilli(((TimestampMessage) 
message).getTimestamp(), 0);
+                if (message instanceof final TimestampMessage tm) {
+                    instant.initFromEpochMilli(tm.getTimestamp(), 0);
                 } else {
                     instant.initFrom(clock != null ? clock : 
ClockFactory.getClock());
                 }
@@ -271,6 +294,7 @@ public class Log4jLogEvent implements LogEvent {
         private void initDefaultContextData() {
             contextDataInjector = ContextDataInjectorFactory.createInjector();
             contextData = contextDataInjector.injectContextData(null, 
ContextDataFactory.createContextData());
+            contextStack = ThreadContext.getImmutableStack();
         }
     }
 
@@ -279,7 +303,7 @@ public class Log4jLogEvent implements LogEvent {
      * @return a new empty builder.
      */
     public static Builder newBuilder() {
-        return new Builder().setLoggerName(Strings.EMPTY);
+        return new Builder();
     }
 
     public Log4jLogEvent() {
@@ -333,12 +357,11 @@ public class Log4jLogEvent implements LogEvent {
         this.threadName = threadName;
         this.threadPriority = threadPriority;
         this.source = source;
-        if (message instanceof LoggerNameAwareMessage) {
-            ((LoggerNameAwareMessage) message).setLoggerName(loggerName);
+        if (message instanceof final LoggerNameAwareMessage awareMessage) {
+            awareMessage.setLoggerName(loggerName);
         }
         this.nanoTime = nanoTime;
-        final long millis =
-                message instanceof TimestampMessage ? ((TimestampMessage) 
message).getTimestamp() : timestampMillis;
+        final long millis = message instanceof final TimestampMessage tm ? 
tm.getTimestamp() : timestampMillis;
         instant.initFromEpochMilli(millis, nanoOfMillisecond);
     }
 
@@ -355,9 +378,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.
@@ -386,7 +417,9 @@ public class Log4jLogEvent implements LogEvent {
     }
 
     public void makeMessageImmutable() {
-        message = new MementoMessage(message.getFormattedMessage(), 
message.getFormat(), message.getParameters());
+        message = message instanceof ReusableMessage reusable
+                ? reusable.memento()
+                : InternalAsyncUtil.makeMessageImmutable(message);
     }
 
     @Override
@@ -526,7 +559,7 @@ public class Log4jLogEvent implements LogEvent {
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
-        final String n = loggerName.isEmpty() ? LoggerConfig.ROOT : loggerName;
+        final String n = Strings.isEmpty(loggerName) ? LoggerConfig.ROOT : 
loggerName;
         sb.append("Logger=").append(n);
         sb.append(" Level=").append(level.name());
         sb.append(" Message=").append(message == null ? null : 
message.getFormattedMessage());
@@ -556,66 +589,60 @@ public class Log4jLogEvent implements LogEvent {
         if (nanoTime != that.nanoTime) {
             return false;
         }
-        if (loggerFqcn != null ? !loggerFqcn.equals(that.loggerFqcn) : 
that.loggerFqcn != null) {
+        if (!Objects.equals(loggerFqcn, that.loggerFqcn)) {
             return false;
         }
-        if (level != null ? !level.equals(that.level) : that.level != null) {
+        if (!Objects.equals(level, that.level)) {
             return false;
         }
-        if (source != null ? !source.equals(that.source) : that.source != 
null) {
+        if (!Objects.equals(source, that.source)) {
             return false;
         }
-        if (marker != null ? !marker.equals(that.marker) : that.marker != 
null) {
+        if (!Objects.equals(marker, that.marker)) {
             return false;
         }
-        if (contextData != null ? !contextData.equals(that.contextData) : 
that.contextData != null) {
+        if (!Objects.equals(contextData, that.contextData)) {
             return false;
         }
         if (!message.equals(that.message)) {
             return false;
         }
-        if (!loggerName.equals(that.loggerName)) {
+        if (!Objects.equals(loggerName, that.loggerName)) {
             return false;
         }
-        if (contextStack != null ? !contextStack.equals(that.contextStack) : 
that.contextStack != null) {
+        if (!Objects.equals(contextStack, that.contextStack)) {
             return false;
         }
         if (threadId != that.threadId) {
             return false;
         }
-        if (threadName != null ? !threadName.equals(that.threadName) : 
that.threadName != null) {
+        if (!Objects.equals(threadName, that.threadName)) {
             return false;
         }
         if (threadPriority != that.threadPriority) {
             return false;
         }
-        if (!Objects.equals(thrown, that.thrown)) {
-            return false;
-        }
-
-        return true;
+        return Objects.equals(thrown, that.thrown);
     }
 
     @Override
     public int hashCode() {
-        // Check:OFF: MagicNumber
-        int result = loggerFqcn != null ? loggerFqcn.hashCode() : 0;
-        result = 31 * result + (marker != null ? marker.hashCode() : 0);
-        result = 31 * result + (level != null ? level.hashCode() : 0);
-        result = 31 * result + loggerName.hashCode();
-        result = 31 * result + message.hashCode();
-        result = 31 * result + instant.hashCode();
-        result = 31 * result + (int) (nanoTime ^ (nanoTime >>> 32));
-        result = 31 * result + (thrown != null ? thrown.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));
-        result = 31 * result + (threadName != null ? threadName.hashCode() : 
0);
-        result = 31 * result + threadPriority;
-        result = 31 * result + (source != null ? source.hashCode() : 0);
-        result = 31 * result + (includeLocation ? 1 : 0);
-        result = 31 * result + (endOfBatch ? 1 : 0);
-        // Check:ON: MagicNumber
-        return result;
+        return Objects.hash(
+                loggerFqcn,
+                marker,
+                level,
+                loggerName,
+                message,
+                instant,
+                nanoTime,
+                thrown,
+                contextData,
+                contextStack,
+                threadId,
+                threadName,
+                threadPriority,
+                source,
+                includeLocation,
+                endOfBatch);
     }
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoLogEvent.java
deleted file mode 100644
index 482f2b6ffd..0000000000
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoLogEvent.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.impl;
-
-import java.util.Objects;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.ThreadContext.ContextStack;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.time.Instant;
-import org.apache.logging.log4j.core.time.MutableInstant;
-import org.apache.logging.log4j.message.LoggerNameAwareMessage;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.ReusableMessage;
-import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.util.InternalApi;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-import org.apache.logging.log4j.util.StringMap;
-import org.apache.logging.log4j.util.Strings;
-import org.jspecify.annotations.Nullable;
-
-/**
- * Immutable copy of a LogEvent.
- *
- * @since 3.0.0
- */
-@InternalApi
-public class MementoLogEvent implements LogEvent {
-    private final String loggerFqcn;
-    private final String loggerName;
-    private final MutableInstant instant = new MutableInstant();
-    private final long nanoTime;
-    private final Level level;
-    private final Marker marker;
-    private boolean locationRequired;
-    private boolean endOfBatch;
-    private final Message message;
-    private final ReadOnlyStringMap contextData;
-    private final ContextStack contextStack;
-    private final @Nullable StackTraceElement source;
-    private final String threadName;
-    private final long threadId;
-    private final int threadPriority;
-    private final Throwable thrown;
-
-    public MementoLogEvent(final LogEvent event) {
-        loggerFqcn = event.getLoggerFqcn();
-        loggerName = event.getLoggerName();
-        instant.initFrom(event.getInstant());
-        nanoTime = event.getNanoTime();
-        level = event.getLevel();
-        marker = event.getMarker();
-        final boolean includeLocation = event.isIncludeLocation();
-        locationRequired = includeLocation;
-        endOfBatch = event.isEndOfBatch();
-        message = mementoOfMessage(event);
-        if (instant.getEpochMillisecond() == 0 && message instanceof 
TimestampMessage) {
-            instant.initFromEpochMilli(((TimestampMessage) 
message).getTimestamp(), 0);
-        }
-        contextData = mementoOfContextData(event.getContextData());
-        contextStack = event.getContextStack();
-        source = includeLocation ? event.getSource() : event.peekSource();
-        threadName = event.getThreadName();
-        threadId = event.getThreadId();
-        threadPriority = event.getThreadPriority();
-        thrown = event.getThrown();
-    }
-
-    private static ReadOnlyStringMap mementoOfContextData(final 
ReadOnlyStringMap readOnlyMap) {
-        if (readOnlyMap instanceof final StringMap stringMap && 
!stringMap.isFrozen()) {
-            final StringMap data = 
ContextDataFactory.createContextData(readOnlyMap);
-            data.freeze();
-            return data;
-        }
-        // otherwise immutable
-        return readOnlyMap;
-    }
-
-    private static Message mementoOfMessage(final LogEvent event) {
-        final Message message = event.getMessage();
-        if (message instanceof LoggerNameAwareMessage) {
-            ((LoggerNameAwareMessage) 
message).setLoggerName(event.getLoggerName());
-        }
-        return message instanceof final ReusableMessage reusable ? 
reusable.memento() : message;
-    }
-
-    @Override
-    public LogEvent toImmutable() {
-        return this;
-    }
-
-    @Override
-    public LogEvent toMemento() {
-        return this;
-    }
-
-    @Override
-    public ReadOnlyStringMap getContextData() {
-        return contextData;
-    }
-
-    @Override
-    public ContextStack getContextStack() {
-        return contextStack;
-    }
-
-    @Override
-    public String getLoggerFqcn() {
-        return loggerFqcn;
-    }
-
-    @Override
-    public Level getLevel() {
-        return level;
-    }
-
-    @Override
-    public String getLoggerName() {
-        return loggerName;
-    }
-
-    @Override
-    public Marker getMarker() {
-        return marker;
-    }
-
-    @Override
-    public Message getMessage() {
-        return message;
-    }
-
-    @Override
-    public long getTimeMillis() {
-        return instant.getEpochMillisecond();
-    }
-
-    @Override
-    public Instant getInstant() {
-        return instant;
-    }
-
-    @Override
-    public StackTraceElement getSource() {
-        return peekSource();
-    }
-
-    @Override
-    public @Nullable StackTraceElement peekSource() {
-        return source;
-    }
-
-    @Override
-    public String getThreadName() {
-        return threadName;
-    }
-
-    @Override
-    public long getThreadId() {
-        return threadId;
-    }
-
-    @Override
-    public int getThreadPriority() {
-        return threadPriority;
-    }
-
-    @Override
-    public Throwable getThrown() {
-        return thrown;
-    }
-
-    @Override
-    public boolean isEndOfBatch() {
-        return endOfBatch;
-    }
-
-    @Override
-    public boolean isIncludeLocation() {
-        return locationRequired;
-    }
-
-    @Override
-    public void setEndOfBatch(boolean endOfBatch) {
-        this.endOfBatch = endOfBatch;
-    }
-
-    @Override
-    public void setIncludeLocation(boolean locationRequired) {
-        this.locationRequired = locationRequired;
-    }
-
-    @Override
-    public long getNanoTime() {
-        return nanoTime;
-    }
-
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        final MementoLogEvent that = (MementoLogEvent) o;
-        return nanoTime == that.nanoTime
-                && locationRequired == that.locationRequired
-                && endOfBatch == that.endOfBatch
-                && threadId == that.threadId
-                && threadPriority == that.threadPriority
-                && Objects.equals(loggerFqcn, that.loggerFqcn)
-                && Objects.equals(loggerName, that.loggerName)
-                && Objects.equals(instant, that.instant)
-                && Objects.equals(level, that.level)
-                && Objects.equals(marker, that.marker)
-                && Objects.equals(message, that.message)
-                && Objects.equals(contextData, that.contextData)
-                && Objects.equals(contextStack, that.contextStack)
-                && Objects.equals(source, that.source)
-                && Objects.equals(threadName, that.threadName)
-                && Objects.equals(thrown, that.thrown);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                loggerFqcn,
-                loggerName,
-                instant,
-                nanoTime,
-                level,
-                marker,
-                locationRequired,
-                endOfBatch,
-                message,
-                contextData,
-                contextStack,
-                source,
-                threadName,
-                threadId,
-                threadPriority,
-                thrown);
-    }
-
-    @Override
-    public String toString() {
-        final String n = loggerName.isEmpty() ? LoggerConfig.ROOT : loggerName;
-        return "Logger=" + n + " Level=" + level.name() + " Message="
-                + (message == null ? Strings.EMPTY : 
message.getFormattedMessage());
-    }
-}
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 e3146426a9..8c0fc21870 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
@@ -22,6 +22,7 @@ import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.ReusableLogEvent;
+import org.apache.logging.log4j.core.async.InternalAsyncUtil;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.core.time.MutableInstant;
@@ -80,6 +81,12 @@ public class MutableLogEvent implements ReusableLogEvent, 
ReusableMessage, Param
         this.parameters = replacementParameters;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   If {@link #isIncludeLocation()} is true, caller information for this 
instance will also be computed.
+     * </p>
+     */
     @Override
     public LogEvent toImmutable() {
         return toMemento();
@@ -211,6 +218,26 @@ public class MutableLogEvent implements ReusableLogEvent, 
ReusableMessage, Param
         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 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/3.x/manual/systemproperties.html#log4j.async.formatMessagesInBackground";>{@code
 log4j.async.formatMessagesInBackground}</a>
+     *     for details.
+     *   </li>
+     * </ol>
+     * @param message The log message. The object passed will be 
<strong>modified</strong> by this method and should not be reused.
+     */
     @Override
     public void setMessage(final Message message) {
         if (message instanceof final ReusableMessage reusable) {
@@ -219,7 +246,7 @@ public class MutableLogEvent implements ReusableLogEvent, 
ReusableMessage, Param
             parameters = reusable.swapParameters(parameters == null ? new 
Object[10] : parameters);
             parameterCount = reusable.getParameterCount();
         } else {
-            this.message = message;
+            this.message = InternalAsyncUtil.makeMessageImmutable(message);
         }
     }
 
@@ -451,25 +478,4 @@ public class MutableLogEvent implements ReusableLogEvent, 
ReusableMessage, Param
     public void setNanoTime(final long nanoTime) {
         this.nanoTime = nanoTime;
     }
-
-    @Override
-    public void initializeBuilder(final Log4jLogEvent.Builder builder) {
-        builder.setContextData(contextData) //
-                .setContextStack(contextStack) //
-                .setEndOfBatch(endOfBatch) //
-                .setIncludeLocation(includeLocation) //
-                .setLevel(getLevel()) // ensure non-null
-                .setLoggerFqcn(loggerFqcn) //
-                .setLoggerName(loggerName) //
-                .setMarker(marker) //
-                .setMessage(memento()) // ensure non-null & immutable
-                .setNanoTime(nanoTime) //
-                .setSource(source) //
-                .setThreadId(threadId) //
-                .setThreadName(threadName) //
-                .setThreadPriority(threadPriority) //
-                .setThrown(getThrown()) //
-                .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 e70d9faed2..b896409a51 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
@@ -130,25 +130,11 @@ public class ReusableLogEventFactory implements 
LogEventFactory {
 
     @Override
     public void recycle(final LogEvent event) {
-        if (event instanceof ReusableLogEvent) {
-            ((ReusableLogEvent) event).clear();
-            if (event instanceof MutableLogEvent) {
-                recycler.release((MutableLogEvent) event);
+        if (event instanceof final ReusableLogEvent reusable) {
+            reusable.clear();
+            if (reusable instanceof final MutableLogEvent mutable) {
+                recycler.release(mutable);
             }
         }
     }
-
-    /**
-     * Switches the {@code reserved} flag off if the specified event is a 
MutableLogEvent, otherwise does nothing.
-     * This flag is used internally to verify that a reusable log event is no 
longer in use and can be reused.
-     * @param logEvent the log event to make available again
-     * @since 2.7
-     * @deprecated use {@link #recycle(LogEvent)}
-     */
-    @Deprecated(since = "3.0.0")
-    public static void release(final LogEvent logEvent) { // LOG4J2-1583
-        if (logEvent instanceof ReusableLogEvent) {
-            ((ReusableLogEvent) logEvent).clear();
-        }
-    }
 }


Reply via email to