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 c7fbbd3255c..abd6e5764ff 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.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 @@ private void clearContextData() {
}
}
}
-
- /**
- * 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 d04c87dc232..725732f46a7 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 void testEquals() {
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 @@ private void different(final String reason, final Log4jLogEvent.Builder builder,
@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 b7ec075be9c..064177b2ad2 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 @@
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 {
*
* Location information for both events might be computed by this method.
*
+ *
+ * Warning: If {@code event.getMessage()} is an instance of {@link ReusableMessage}, this method
+ * remove the parameter references from the original message. Callers should:
+ *
+ *
+ * - Either make sure that the {@code event} will not be used again.
+ * - Or call {@link LogEvent#toImmutable()} before calling this method.
+ *
+ * @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 12e591aba04..0226ea792a9 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 @@
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 795424bd930..e7355476934 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.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 @@
* Consider this class private.
*
*/
+@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 806826f8abc..16078728bb3 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.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 @@
*/
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 {
+ // 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 Builder() {
initDefaultContextData();
}
+ /**
+ * Initializes the builder with an immutable 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 Log4jLogEvent build() {
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 @@ private void initTimeFields() {
private void initDefaultContextData() {
contextDataInjector = ContextDataInjectorFactory.createInjector();
contextData = contextDataInjector.injectContextData(null, ContextDataFactory.createContextData());
+ contextStack = ThreadContext.getImmutableStack();
}
}
@@ -279,7 +303,7 @@ private void initDefaultContextData() {
* @return a new empty builder.
*/
public static Builder newBuilder() {
- return new Builder().setLoggerName(Strings.EMPTY);
+ return new Builder();
}
public Log4jLogEvent() {
@@ -333,12 +357,11 @@ private Log4jLogEvent(
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 Log4jLogEvent toImmutable() {
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 Message getMessage() {
}
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 long getNanoTime() {
@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 boolean equals(final Object o) {
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 482f2b6ffd8..00000000000
--- 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 e3146426a95..8c0fc218701 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.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 MutableLogEvent(final StringBuilder msgText, final Object[] replacementPa
this.parameters = replacementParameters;
}
+ /**
+ * {@inheritDoc}
+ *
+ * If {@link #isIncludeLocation()} is true, caller information for this instance will also be computed.
+ *
+ */
@Override
public LogEvent toImmutable() {
return toMemento();
@@ -211,6 +218,26 @@ public Message getMessage() {
return message;
}
+ /**
+ * Sets the log message of the event.
+ *
+ *
+ * Warning: This method mutates the state of the {@code message}
+ * parameter:
+ *
+ *
+ * -
+ * If the message is a {@link ReusableMessage}, this method will remove its
+ * parameter references, which prevents it from being used again.
+ *
+ * -
+ * Otherwise the lazy {@link Message#getFormattedMessage()} message might be called.
+ * See {@code log4j.async.formatMessagesInBackground}
+ * for details.
+ *
+ *
+ * @param message The log message. The object passed will be modified 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 void setMessage(final Message message) {
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 long getNanoTime() {
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 e70d9faed23..b896409a511 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 LogEvent createEvent(
@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();
- }
- }
}