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