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: + *

+ *
    + *
  1. Either make sure that the {@code event} will not be used again.
  2. + *
  3. Or call {@link LogEvent#toImmutable()} before calling this method.
  4. + *
+ * @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: + *

+ *
    + *
  1. + * If the message is a {@link ReusableMessage}, this method will remove its + * parameter references, which prevents it from being used again. + *
  2. + *
  3. + * Otherwise the lazy {@link Message#getFormattedMessage()} message might be called. + * See {@code log4j.async.formatMessagesInBackground} + * for details. + *
  4. + *
+ * @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(); - } - } }