diff --git a/docs/2.17.0-interpolation.md b/docs/2.17.0-interpolation.md
index 76c496da3f8..ae43e0f6e38 100644
--- a/docs/2.17.0-interpolation.md
+++ b/docs/2.17.0-interpolation.md
@@ -1,3 +1,22 @@
+
+
I'd like to go into detail on some of the changes in 2.17.0, why they're so important, and how they relate to both [CVE-2021-45046](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046) and [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105).
The substitution of untrusted log data allowed access to code that was never meant to be exposed. Lookups should be triggered only by configuration and the logging framework (including custom layout/appender/etc plugins). Not by user-provided inputs.
@@ -153,4 +172,4 @@ The fix in this case is to inline the `fileName` property value into the `Rollin
-```
\ No newline at end of file
+```
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
index 0378e02c880..0787dbbd455 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
@@ -166,32 +166,36 @@ public String toSerializable(final LogEvent event) {
// so we generate the message first
final String message = messageLayout != null ? messageLayout.toSerializable(event)
: event.getMessage().getFormattedMessage();
- final StringBuilder buf = getStringBuilder();
-
- buf.append('<');
- buf.append(Priority.getPriority(facility, event.getLevel()));
- buf.append('>');
-
- if (header) {
- final int index = buf.length() + 4;
- dateConverter.format(event, buf);
- // RFC 3164 says leading space, not leading zero on days 1-9
- if (buf.charAt(index) == '0') {
- buf.setCharAt(index, Chars.SPACE);
+ final StringBuilder buf = acquireStringBuilder();
+
+ try {
+ buf.append('<');
+ buf.append(Priority.getPriority(facility, event.getLevel()));
+ buf.append('>');
+
+ if (header) {
+ final int index = buf.length() + 4;
+ dateConverter.format(event, buf);
+ // RFC 3164 says leading space, not leading zero on days 1-9
+ if (buf.charAt(index) == '0') {
+ buf.setCharAt(index, Chars.SPACE);
+ }
+
+ buf.append(Chars.SPACE);
+ buf.append(localHostname);
+ buf.append(Chars.SPACE);
}
- buf.append(Chars.SPACE);
- buf.append(localHostname);
- buf.append(Chars.SPACE);
- }
+ if (facilityPrinting) {
+ buf.append(facility != null ? facility.name().toLowerCase() : "user").append(':');
+ }
- if (facilityPrinting) {
- buf.append(facility != null ? facility.name().toLowerCase() : "user").append(':');
+ buf.append(message);
+ // TODO: splitting message into 1024 byte chunks?
+ return buf.toString();
+ } finally {
+ releaseStringBuilder(buf);
}
-
- buf.append(message);
- // TODO: splitting message into 1024 byte chunks?
- return buf.toString();
}
/**
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java
index 4eb629c62a7..4f48fdd0912 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java
@@ -16,6 +16,12 @@
*/
package org.apache.log4j.layout;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Objects;
+
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
@@ -28,12 +34,6 @@
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.Strings;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Objects;
-
/**
* Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations.
*
@@ -75,16 +75,24 @@ public boolean isProperties() {
@Override
public void encode(final LogEvent event, final ByteBufferDestination destination) {
- final StringBuilder text = getStringBuilder();
- formatTo(event, text);
- getStringBuilderEncoder().encode(text, destination);
+ final StringBuilder text = acquireStringBuilder();
+ try {
+ formatTo(event, text);
+ getStringBuilderEncoder().encode(text, destination);
+ } finally {
+ releaseStringBuilder(text);
+ }
}
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder text = getStringBuilder();
- formatTo(event, text);
- return text.toString();
+ final StringBuilder text = acquireStringBuilder();
+ try {
+ formatTo(event, text);
+ return text.toString();
+ } finally {
+ releaseStringBuilder(text);
+ }
}
private void formatTo(final LogEvent event, final StringBuilder buf) {
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
index f5e293134e7..6ac576f7a88 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.logging.log4j.message;
+import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@@ -25,35 +27,39 @@
*/
public class ReusableMessageFactoryTest {
+ private ReusableMessageFactory factory;
+
+ @BeforeEach
+ void setUp() {
+ factory = new ReusableMessageFactory(ThreadLocalRecyclerFactory.getInstance());
+ }
+
@Test
public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception {
- final ReusableMessageFactory factory = new ReusableMessageFactory();
final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6);
assertNotSame(message1, message2);
- ReusableMessageFactory.release(message1);
- ReusableMessageFactory.release(message2);
+ factory.recycle(message1);
+ factory.recycle(message2);
}
@Test
public void testCreateEventReturnsSameInstance() throws Exception {
- final ReusableMessageFactory factory = new ReusableMessageFactory();
final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
- ReusableMessageFactory.release(message1);
+ factory.recycle(message1);
final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6);
assertSame(message1, message2);
- ReusableMessageFactory.release(message2);
+ factory.recycle(message2);
final Message message3 = factory.newMessage("text, AAA={} BBB={} p2={} p3={}", 9, 8, 7, 6);
assertSame(message2, message3);
- ReusableMessageFactory.release(message3);
+ factory.recycle(message3);
}
private void assertReusableParameterizeMessage(final Message message, final String txt, final Object[] params) {
assertTrue(message instanceof ReusableParameterizedMessage);
final ReusableParameterizedMessage msg = (ReusableParameterizedMessage) message;
- assertTrue(msg.reserved, "reserved");
assertEquals(txt, msg.getFormat());
assertEquals(msg.getParameterCount(), params.length, "count");
@@ -65,7 +71,6 @@ private void assertReusableParameterizeMessage(final Message message, final Stri
@Test
public void testCreateEventOverwritesFields() throws Exception {
- final ReusableMessageFactory factory = new ReusableMessageFactory();
final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
assertReusableParameterizeMessage(message1, "text, p0={} p1={} p2={} p3={}", new Object[]{
new Integer(1), //
@@ -74,7 +79,7 @@ public void testCreateEventOverwritesFields() throws Exception {
new Integer(4), //
});
- ReusableMessageFactory.release(message1);
+ factory.recycle(message1);
final Message message2 = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4);
assertReusableParameterizeMessage(message1, "other, A={} B={} C={} D={}", new Object[]{
new Integer(1), //
@@ -83,12 +88,11 @@ public void testCreateEventOverwritesFields() throws Exception {
new Integer(4), //
});
assertSame(message1, message2);
- ReusableMessageFactory.release(message2);
+ factory.recycle(message2);
}
@Test
public void testCreateEventReturnsThreadLocalInstance() throws Exception {
- final ReusableMessageFactory factory = new ReusableMessageFactory();
final Message[] message1 = new Message[1];
final Message[] message2 = new Message[1];
final Thread t1 = new Thread("THREAD 1") {
@@ -123,8 +127,8 @@ public void run() {
new Integer(3), //
new Integer(4), //
});
- ReusableMessageFactory.release(message1[0]);
- ReusableMessageFactory.release(message2[0]);
+ factory.recycle(message1[0]);
+ factory.recycle(message2[0]);
}
}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java
new file mode 100644
index 00000000000..14428d20c97
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.spi;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junitpioneer.jupiter.params.IntRangeSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ThreadLocalRecyclerFactoryTest {
+
+ static class RecyclableObject {
+ boolean using;
+ boolean returned;
+ }
+
+ private Recycler recycler;
+
+ private Queue getRecyclerQueue() {
+ return ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue();
+ }
+
+ @BeforeEach
+ void setUp() {
+ recycler = ThreadLocalRecyclerFactory.getInstance()
+ .create(RecyclableObject::new, o -> {
+ o.using = true;
+ o.returned = false;
+ }, o -> {
+ o.returned = true;
+ o.using = false;
+ });
+ }
+
+ @ParameterizedTest
+ @IntRangeSource(from = 1, to = ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE, closed = true)
+ void nestedAcquiresDoNotInterfere(int acquisitionCount) {
+ // pool should start empty
+ assertThat(getRecyclerQueue()).isEmpty();
+
+ final List acquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .collect(Collectors.toList());
+
+ // still nothing returned to pool
+ assertThat(getRecyclerQueue()).isEmpty();
+
+ // don't want any duplicate instances
+ assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+ acquiredObjects.forEach(recycler::release);
+
+ // and now they should be back in the pool
+ assertThat(getRecyclerQueue()).hasSize(acquisitionCount);
+
+ // then reacquire them to see that they're still the same object as we've filled in
+ // the thread-local queue with returned objects
+ final List reacquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .collect(Collectors.toList());
+
+ assertThat(reacquiredObjects).containsExactlyElementsOf(acquiredObjects);
+ }
+
+ @Test
+ void nestedAcquiresPastMaximumQueueSizeShouldDiscardExtraReleases() {
+ assertThat(getRecyclerQueue()).isEmpty();
+
+ // simulate a massively callstack with tons of logging
+ final List acquiredObjects = IntStream.range(0, 1024)
+ .mapToObj(i -> recycler.acquire())
+ .collect(Collectors.toList());
+
+ // still nothing returned to pool
+ assertThat(getRecyclerQueue()).isEmpty();
+
+ // don't want any duplicate instances
+ assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+ acquiredObjects.forEach(recycler::release);
+
+ // upon return, we should only have ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE retained for future use
+ assertThat(getRecyclerQueue()).hasSize(ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE);
+ }
+}
diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml
index f4ab48371a5..88439843b97 100644
--- a/log4j-api/pom.xml
+++ b/log4j-api/pom.xml
@@ -42,6 +42,11 @@
org.osgi.resourceprovided
+
+ org.jctools
+ jctools-core
+ true
+
@@ -51,10 +56,6 @@
org.apache.logging.log4j.*
-
- sun.reflect;resolution:=optional,
- *
- org.apache.logging.log4j.util.Activator
<_fixupmessages>"Classes found in the wrong directory";is:=warning
diff --git a/log4j-api/src/main/java/module-info.java b/log4j-api/src/main/java/module-info.java
index 57f500e63e1..bb8d5dc0a80 100644
--- a/log4j-api/src/main/java/module-info.java
+++ b/log4j-api/src/main/java/module-info.java
@@ -38,8 +38,12 @@
exports org.apache.logging.log4j.status;
exports org.apache.logging.log4j.util;
+ // optional support for formatting SQL date/time classes in messages properly
requires static java.sql;
+ // optional support for running in an OSGi environment
requires static org.osgi.framework;
+ // optional support for using JCTools in a Recycler
+ requires static org.jctools.core;
uses org.apache.logging.log4j.spi.Provider;
uses PropertySource;
uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
index cd472c43e3e..0b7f3d65ee5 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
@@ -24,6 +24,7 @@
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.InternalApi;
import org.apache.logging.log4j.util.LambdaUtil;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
@@ -33,6 +34,7 @@
/**
* Collects data for a log event and then logs it. This class should be considered private.
*/
+@InternalApi
public class DefaultLogBuilder implements BridgeAware, LogBuilder {
private static final String FQCN = DefaultLogBuilder.class.getName();
@@ -44,20 +46,11 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
private Marker marker;
private Throwable throwable;
private StackTraceElement location;
- private volatile boolean inUse;
private final long threadId;
private String fqcn = FQCN;
- public DefaultLogBuilder(final Logger logger, final Level level) {
- this.logger = logger;
- this.level = level;
- this.threadId = Thread.currentThread().getId();
- this.inUse = true;
- }
-
public DefaultLogBuilder(final Logger logger) {
this.logger = logger;
- this.inUse = false;
this.threadId = Thread.currentThread().getId();
}
@@ -66,44 +59,36 @@ public void setEntryPoint(String fqcn) {
this.fqcn = fqcn;
}
- /**
- * This method should be considered internal. It is used to reset the LogBuilder for a new log message.
- * @param level The logging level for this event.
- * @return This LogBuilder instance.
- */
- public LogBuilder reset(final Level level) {
- this.inUse = true;
+ @InternalApi
+ public LogBuilder atLevel(final Level level) {
this.level = level;
- this.marker = null;
- this.throwable = null;
- this.location = null;
return this;
}
+ @Override
public LogBuilder withMarker(final Marker marker) {
this.marker = marker;
return this;
}
+ @Override
public LogBuilder withThrowable(final Throwable throwable) {
this.throwable = throwable;
return this;
}
+ @Override
public LogBuilder withLocation() {
location = StackLocatorUtil.getStackTraceElement(2);
return this;
}
+ @Override
public LogBuilder withLocation(final StackTraceElement location) {
this.location = location;
return this;
}
- public boolean isInUse() {
- return inUse;
- }
-
@Override
public void log(final Message message) {
if (isValid()) {
@@ -246,16 +231,15 @@ private void logMessage(final Message message) {
try {
logger.logMessage(level, marker, fqcn, location, message, throwable);
} finally {
- inUse = false;
+ // recycle self
+ this.level = null;
+ this.marker = null;
+ this.throwable = null;
+ this.location = null;
}
}
private boolean isValid() {
- if (!inUse) {
- LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}",
- StackLocatorUtil.getCallerClass(2));
- return false ;
- }
if (this.threadId != Thread.currentThread().getId()) {
LOGGER.warn("LogBuilder can only be used on the owning thread. {}",
StackLocatorUtil.getCallerClass(2));
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
index abe07ba4bc4..142d63b1d36 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
@@ -18,6 +18,8 @@
/**
* Creates messages. Implementations can provide different message format syntaxes.
+ * If messages created by an implementation are reusable, then the {@link #recycle(Message)} method must
+ * be implemented and consistently used for returning objects to be reused.
*
* @see ParameterizedMessageFactory
* @see StringFormatterMessageFactory
@@ -236,4 +238,16 @@ default Message newMessage(final String message, final Object p0, final Object p
final Object p6, final Object p7, final Object p8, final Object p9) {
return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 });
}
+
+ /**
+ * Recycles a message back for potential reuse or cleanup.
+ *
+ * @since 3.0.0
+ * @see org.apache.logging.log4j.spi.Recycler
+ */
+ default void recycle(Message message) {
+ if (message instanceof ReusableMessage) {
+ ((ReusableMessage) message).clear();
+ }
+ }
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index 7bf0c452f98..4e494dd82ea 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -16,15 +16,20 @@
*/
package org.apache.logging.log4j.message;
+import org.apache.logging.log4j.spi.LoggingSystem;
+import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.RecyclerFactory;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
* Implementation of the {@link MessageFactory} interface that avoids allocating temporary objects where possible.
* Message instances are cached in a ThreadLocal and reused when a new message is requested within the same thread.
+ * Messages returned from this factory must be {@linkplain #recycle(Message) recycled} when done using.
* @see ParameterizedMessageFactory
* @see ReusableSimpleMessage
* @see ReusableObjectMessage
* @see ReusableParameterizedMessage
+ * @see Recycler
* @since 2.6
*/
@PerformanceSensitive("allocation")
@@ -35,42 +40,31 @@ public final class ReusableMessageFactory implements MessageFactory {
*/
public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory();
- private static final ThreadLocal threadLocalParameterized = new ThreadLocal<>();
- private static final ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>();
- private static final ThreadLocal threadLocalObjectMessage = new ThreadLocal<>();
+ private final Recycler parameterizedMessageRecycler;
+ private final Recycler simpleMessageRecycler;
+ private final Recycler objectMessageRecycler;
/**
- * Constructs a message factory.
+ * Constructs a message factory using the default {@link RecyclerFactory}.
*/
public ReusableMessageFactory() {
- super();
- }
-
- private static ReusableParameterizedMessage getParameterized() {
- ReusableParameterizedMessage result = threadLocalParameterized.get();
- if (result == null) {
- result = new ReusableParameterizedMessage();
- threadLocalParameterized.set(result);
- }
- return result.reserved ? new ReusableParameterizedMessage().reserve() : result.reserve();
- }
-
- private static ReusableSimpleMessage getSimple() {
- ReusableSimpleMessage result = threadLocalSimpleMessage.get();
- if (result == null) {
- result = new ReusableSimpleMessage();
- threadLocalSimpleMessage.set(result);
- }
- return result;
+ this(LoggingSystem.getRecyclerFactory());
}
- private static ReusableObjectMessage getObject() {
- ReusableObjectMessage result = threadLocalObjectMessage.get();
- if (result == null) {
- result = new ReusableObjectMessage();
- threadLocalObjectMessage.set(result);
- }
- return result;
+ public ReusableMessageFactory(final RecyclerFactory recyclerFactory) {
+ super();
+ parameterizedMessageRecycler = recyclerFactory.create(
+ ReusableParameterizedMessage::new,
+ RecyclerFactory.getDefaultCleaner(),
+ ReusableParameterizedMessage::clear);
+ simpleMessageRecycler = recyclerFactory.create(
+ ReusableSimpleMessage::new,
+ RecyclerFactory.getDefaultCleaner(),
+ ReusableSimpleMessage::clear);
+ objectMessageRecycler = recyclerFactory.create(
+ ReusableObjectMessage::new,
+ RecyclerFactory.getDefaultCleaner(),
+ ReusableObjectMessage::clear);
}
/**
@@ -89,9 +83,21 @@ public static void release(final Message message) { // LOG4J2-1583
}
}
+ @Override
+ public void recycle(final Message message) {
+ // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today!
+ if (message instanceof ReusableParameterizedMessage) {
+ parameterizedMessageRecycler.release((ReusableParameterizedMessage) message);
+ } else if (message instanceof ReusableObjectMessage) {
+ objectMessageRecycler.release((ReusableObjectMessage) message);
+ } else if (message instanceof ReusableSimpleMessage) {
+ simpleMessageRecycler.release((ReusableSimpleMessage) message);
+ }
+ }
+
@Override
public Message newMessage(final CharSequence charSequence) {
- final ReusableSimpleMessage result = getSimple();
+ final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
result.set(charSequence);
return result;
}
@@ -107,64 +113,64 @@ public Message newMessage(final CharSequence charSequence) {
*/
@Override
public Message newMessage(final String message, final Object... params) {
- return getParameterized().set(message, params);
+ return parameterizedMessageRecycler.acquire().set(message, params);
}
@Override
public Message newMessage(final String message, final Object p0) {
- return getParameterized().set(message, p0);
+ return parameterizedMessageRecycler.acquire().set(message, p0);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1) {
- return getParameterized().set(message, p0, p1);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
- return getParameterized().set(message, p0, p1, p2);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2,
final Object p3) {
- return getParameterized().set(message, p0, p1, p2, p3);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4) {
- return getParameterized().set(message, p0, p1, p2, p3, p4);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5) {
- return getParameterized().set(message, p0, p1, p2, p3, p4, p5);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6) {
- return getParameterized().set(message, p0, p1, p2, p3, p4, p5, p6);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6, final Object p7) {
- return getParameterized().set(message, p0, p1, p2, p3, p4, p5, p6, p7);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6, p7);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) {
- return getParameterized().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
}
@Override
public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) {
- return getParameterized().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
+ return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
/**
@@ -177,7 +183,7 @@ public Message newMessage(final String message, final Object p0, final Object p1
*/
@Override
public Message newMessage(final String message) {
- final ReusableSimpleMessage result = getSimple();
+ final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
result.set(message);
return result;
}
@@ -193,7 +199,7 @@ public Message newMessage(final String message) {
*/
@Override
public Message newMessage(final Object message) {
- final ReusableObjectMessage result = getObject();
+ final ReusableObjectMessage result = objectMessageRecycler.acquire();
result.set(message);
return result;
}
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 85fd6d74989..9ec59f6b67c 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
@@ -18,6 +18,9 @@
import java.util.Arrays;
+import org.apache.logging.log4j.spi.LoggingSystem;
+import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.RecyclerFactory;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
@@ -34,7 +37,6 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV
private static final int MIN_BUILDER_SIZE = 512;
private static final int MAX_PARMS = 10;
- private ThreadLocal buffer; // non-static: LOG4J2-1583
private String messagePattern;
private int argCount;
@@ -43,12 +45,25 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV
private Object[] varargs;
private Object[] params = new Object[MAX_PARMS];
private Throwable throwable;
- boolean reserved = false; // LOG4J2-1583 prevent scrambled logs with nested logging calls
+
+ private final Recycler bufferRecycler; // non-static: LOG4J2-1583
/**
* Creates a reusable message.
*/
public ReusableParameterizedMessage() {
+ this(LoggingSystem.getRecyclerFactory());
+ }
+
+ public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) {
+ bufferRecycler = recyclerFactory.create(
+ () -> {
+ final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length();
+ return new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2));
+ },
+ buffer -> buffer.setLength(0),
+ buffer -> StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE)
+ );
}
private Object[] getTrimmedParams() {
@@ -299,25 +314,13 @@ public Throwable getThrowable() {
*/
@Override
public String getFormattedMessage() {
- final StringBuilder sb = getBuffer();
- formatTo(sb);
- final String result = sb.toString();
- StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE);
- return result;
- }
-
- private StringBuilder getBuffer() {
- if (buffer == null) {
- buffer = new ThreadLocal<>();
- }
- StringBuilder result = buffer.get();
- if (result == null) {
- final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length();
- result = new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2));
- buffer.set(result);
+ final StringBuilder sb = bufferRecycler.acquire();
+ try {
+ formatTo(sb);
+ return sb.toString();
+ } finally {
+ bufferRecycler.release(sb);
}
- result.setLength(0);
- return result;
}
@Override
@@ -329,16 +332,6 @@ public void formatTo(final StringBuilder builder) {
}
}
- /**
- * Sets the reserved flag to true and returns this object.
- * @return this object
- * @since 2.7
- */
- ReusableParameterizedMessage reserve() {
- reserved = true;
- return this;
- }
-
@Override
public String toString() {
return "ReusableParameterizedMessage[messagePattern=" + getFormat() + ", stringArgs=" +
@@ -349,7 +342,6 @@ public String toString() {
public void clear() { // LOG4J2-1583
// This method does not clear parameter values, those are expected to be swapped to a
// reusable message, which is responsible for clearing references.
- reserved = false;
varargs = null;
messagePattern = null;
throwable = null;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index f6d5a220773..6a6d4368a94 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -26,11 +26,9 @@
import org.apache.logging.log4j.message.FlowMessageFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.ReusableMessageFactory;
import org.apache.logging.log4j.message.StringFormattedMessage;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Cast;
-import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.LambdaUtil;
import org.apache.logging.log4j.util.MessageSupplier;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -84,7 +82,8 @@ public abstract class AbstractLogger implements ExtendedLogger {
private final MessageFactory messageFactory;
private final FlowMessageFactory flowMessageFactory;
private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031
- protected final transient ThreadLocal logBuilder;
+ protected final Recycler recycler = LoggingSystem.getRecyclerFactory()
+ .create(() -> new DefaultLogBuilder(this));
/**
@@ -95,7 +94,6 @@ public AbstractLogger() {
this.name = canonicalName != null ? canonicalName : getClass().getName();
this.messageFactory = LoggingSystem.getMessageFactory();
this.flowMessageFactory = LoggingSystem.getFlowMessageFactory();
- this.logBuilder = new LocalLogBuilder(this);
}
/**
@@ -117,7 +115,6 @@ public AbstractLogger(final String name, final MessageFactory messageFactory) {
this.name = name;
this.messageFactory = messageFactory == null ? LoggingSystem.getMessageFactory() : messageFactory;
this.flowMessageFactory = LoggingSystem.getFlowMessageFactory();
- this.logBuilder = new LocalLogBuilder(this);
}
/**
@@ -1943,7 +1940,7 @@ public void logMessage(final Level level, final Marker marker, final String fqcn
handleLogMessageException(ex, fqcn, message);
} finally {
decrementRecursionDepth();
- ReusableMessageFactory.release(message);
+ messageFactory.recycle(message);
}
}
@@ -1977,7 +1974,7 @@ private void logMessageSafely(final String fqcn, final Level level, final Marker
logMessageTrackRecursion(fqcn, level, marker, msg, throwable);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
- ReusableMessageFactory.release(msg);
+ messageFactory.recycle(msg);
}
}
@@ -2742,11 +2739,8 @@ public LogBuilder atFatal() {
*/
@Override
public LogBuilder always() {
- final DefaultLogBuilder builder = logBuilder.get();
- if (builder.isInUse()) {
- return new DefaultLogBuilder(this);
- }
- return builder.reset(Level.OFF);
+ final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire();
+ return builder.atLevel(Level.OFF);
}
/**
@@ -2757,26 +2751,11 @@ public LogBuilder always() {
@Override
public LogBuilder atLevel(final Level level) {
if (isEnabled(level)) {
- return (getLogBuilder(level).reset(level));
+ final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire();
+ return builder.atLevel(level);
} else {
return LogBuilder.NOOP;
}
}
- private DefaultLogBuilder getLogBuilder(final Level level) {
- final DefaultLogBuilder builder = logBuilder.get();
- return Constants.isThreadLocalsEnabled() && !builder.isInUse() ? builder : new DefaultLogBuilder(this, level);
- }
-
- private static class LocalLogBuilder extends ThreadLocal {
- private final AbstractLogger logger;
- LocalLogBuilder(final AbstractLogger logger) {
- this.logger = logger;
- }
-
- @Override
- protected DefaultLogBuilder initialValue() {
- return new DefaultLogBuilder(logger);
- }
- }
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
similarity index 67%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecyclerFactory.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
index 2a96d98fb84..3ca81b521d7 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecyclerFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
@@ -14,11 +14,16 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.util;
+package org.apache.logging.log4j.spi;
import java.util.function.Consumer;
import java.util.function.Supplier;
+/**
+ * Recycler strategy which doesn't recycle anything; all instances are freshly created.
+ *
+ * @since 3.0.0
+ */
public class DummyRecyclerFactory implements RecyclerFactory {
private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory();
@@ -32,8 +37,26 @@ public static DummyRecyclerFactory getInstance() {
@Override
public Recycler create(
final Supplier supplier,
- final Consumer cleaner) {
+ final Consumer lazyCleaner,
+ final Consumer eagerCleaner) {
return new DummyRecycler<>(supplier);
}
+ private static class DummyRecycler implements Recycler {
+
+ private final Supplier supplier;
+
+ private DummyRecycler(final Supplier supplier) {
+ this.supplier = supplier;
+ }
+
+ @Override
+ public V acquire() {
+ return supplier.get();
+ }
+
+ @Override
+ public void release(final V value) {}
+
+ }
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
index 3f7981fc629..d3331a67723 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
@@ -99,6 +99,7 @@ public class LoggingSystem {
() -> getProvider().createContextMap(environment));
private final Lazy> threadContextStackFactoryLazy = environmentLazy.map(environment ->
() -> getProvider().createContextStack(environment));
+ private final Lazy recyclerFactoryLazy = Lazy.relaxed(RecyclerFactories::getDefault);
/**
* Acquires a lock on the initialization of locating a logging system provider. This lock should be
@@ -178,6 +179,10 @@ public void setThreadContextStackFactory(final Supplier thre
threadContextStackFactoryLazy.set(threadContextStackFactory);
}
+ public void setRecyclerFactory(final RecyclerFactory factory) {
+ recyclerFactoryLazy.set(factory);
+ }
+
/**
* Gets the LoggingSystem instance.
*/
@@ -215,6 +220,10 @@ public static ThreadContextStack createContextStack() {
return getInstance().threadContextStackFactoryLazy.value().get();
}
+ public static RecyclerFactory getRecyclerFactory() {
+ return getInstance().recyclerFactoryLazy.value();
+ }
+
private static List loadDefaultProviders() {
return ServiceRegistry.getInstance()
.getServices(Provider.class, MethodHandles.lookup(), provider -> validVersion(provider.getVersions()));
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java
new file mode 100644
index 00000000000..3387ee47803
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java
@@ -0,0 +1,87 @@
+/*
+ * 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.spi;
+
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.util.QueueFactory;
+
+public class QueueingRecyclerFactory implements RecyclerFactory {
+
+ private final QueueFactory queueFactory;
+
+ public QueueingRecyclerFactory(final QueueFactory queueFactory) {
+ this.queueFactory = queueFactory;
+ }
+
+ @Override
+ public Recycler create(
+ final Supplier supplier,
+ final Consumer lazyCleaner,
+ final Consumer eagerCleaner) {
+ final Queue queue = queueFactory.create();
+ return new QueueingRecycler<>(supplier, lazyCleaner, eagerCleaner, queue);
+ }
+
+ // Visible for tests.
+ static class QueueingRecycler implements Recycler {
+
+ private final Supplier supplier;
+
+ private final Consumer lazyCleaner;
+
+ private final Consumer eagerCleaner;
+
+ private final Queue queue;
+
+ private QueueingRecycler(
+ final Supplier supplier,
+ final Consumer lazyCleaner,
+ final Consumer eagerCleaner,
+ final Queue queue) {
+ this.supplier = supplier;
+ this.lazyCleaner = lazyCleaner;
+ this.eagerCleaner = eagerCleaner;
+ this.queue = queue;
+ }
+
+ // Visible for tests.
+ Queue getQueue() {
+ return queue;
+ }
+
+ @Override
+ public V acquire() {
+ final V value = queue.poll();
+ if (value == null) {
+ return supplier.get();
+ } else {
+ lazyCleaner.accept(value);
+ return value;
+ }
+ }
+
+ @Override
+ public void release(final V value) {
+ eagerCleaner.accept(value);
+ queue.offer(value);
+ }
+
+ }
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
similarity index 52%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecycler.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
index 8936a8a682c..5a291baf719 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/DummyRecycler.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
@@ -14,24 +14,30 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.util;
+package org.apache.logging.log4j.spi;
-import java.util.function.Supplier;
-
-public class DummyRecycler implements Recycler {
-
- private final Supplier supplier;
-
- public DummyRecycler(final Supplier supplier) {
- this.supplier = supplier;
- }
+/**
+ * Strategy for recycling objects. This is primarily useful for heavyweight objects and buffers.
+ *
+ * @param the recyclable type
+ * @since 3.0.0
+ */
+public interface Recycler {
- @Override
- public V acquire() {
- return supplier.get();
- }
+ /**
+ * Acquires an instance of V. This may either be a fresh instance of V or a recycled instance of V.
+ * Recycled instances will be modified by their cleanup function before being returned.
+ *
+ * @return an instance of V to be used
+ */
+ V acquire();
- @Override
- public void release(final V value) {}
+ /**
+ * Releases an instance of V. This allows the instance to be recycled and later reacquired for new
+ * purposes.
+ *
+ * @param value an instance of V no longer being used
+ */
+ void release(V value);
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java
new file mode 100644
index 00000000000..665bd2b215a
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java
@@ -0,0 +1,127 @@
+/*
+ * 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.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.util.QueueFactory;
+import org.apache.logging.log4j.util.Queues;
+import org.apache.logging.log4j.util.StringParameterParser;
+
+import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
+
+public final class RecyclerFactories {
+
+ private RecyclerFactories() {}
+
+ private static int getDefaultCapacity() {
+ return Math.max(
+ 2 * Runtime.getRuntime().availableProcessors() + 1,
+ 8);
+ }
+
+ public static RecyclerFactory getDefault() {
+ return isThreadLocalsEnabled()
+ ? ThreadLocalRecyclerFactory.getInstance()
+ : new QueueingRecyclerFactory(Queues.MPMC.factory(getDefaultCapacity()));
+ }
+
+ public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
+
+ // TLA-, MPMC-, or ABQ-based queueing factory -- if nothing is specified.
+ if (recyclerFactorySpec == null) {
+ return getDefault();
+ }
+
+ // Is a dummy factory requested?
+ else if (recyclerFactorySpec.equals("dummy")) {
+ return DummyRecyclerFactory.getInstance();
+ }
+
+ // Is a TLA factory requested?
+ else if (recyclerFactorySpec.equals("threadLocal")) {
+ return ThreadLocalRecyclerFactory.getInstance();
+ }
+
+ // Is a queueing factory requested?
+ else if (recyclerFactorySpec.startsWith("queue")) {
+
+ // Determine the default capacity.
+ final int defaultCapacity = getDefaultCapacity();
+
+ return readQueueingRecyclerFactory(recyclerFactorySpec, defaultCapacity);
+ }
+
+ // Bogus input, bail out.
+ else {
+ throw new IllegalArgumentException(
+ "invalid recycler factory: " + recyclerFactorySpec);
+ }
+
+ }
+
+ private static RecyclerFactory readQueueingRecyclerFactory(
+ final String recyclerFactorySpec,
+ final int defaultCapacity) {
+
+ // Parse the spec.
+ final String queueFactorySpec = recyclerFactorySpec.substring(
+ "queue".length() +
+ (recyclerFactorySpec.startsWith("queue:")
+ ? 1
+ : 0));
+ final Map parsedValues =
+ StringParameterParser.parse(
+ queueFactorySpec, Set.of("supplier", "capacity"));
+
+ // Read the capacity.
+ final StringParameterParser.Value capacityValue = parsedValues.get("capacity");
+ final int capacity;
+ if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) {
+ capacity = defaultCapacity;
+ } else {
+ try {
+ capacity = Integer.parseInt(capacityValue.toString());
+ } catch (final NumberFormatException error) {
+ throw new IllegalArgumentException(
+ "failed reading capacity in queueing recycler " +
+ "factory: " + queueFactorySpec, error);
+ }
+ }
+
+ // Read the supplier path.
+ final StringParameterParser.Value supplierValue = parsedValues.get("supplier");
+ final String supplierPath;
+ if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) {
+ supplierPath = null;
+ } else {
+ supplierPath = supplierValue.toString();
+ }
+
+ // Execute the read spec.
+ final QueueFactory queueFactory;
+ if (supplierPath != null) {
+ queueFactory = Queues.createQueueFactory(supplierPath, capacity);
+ } else {
+ queueFactory = Queues.MPMC.factory(capacity);
+ }
+
+ return new QueueingRecyclerFactory(queueFactory);
+ }
+
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
new file mode 100644
index 00000000000..5bed3016911
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
@@ -0,0 +1,82 @@
+/*
+ * 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.spi;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Factory for {@link Recycler} strategies. Depending on workloads, different instance recycling strategies may be
+ * most performant. For example, traditional multithreaded workloads may benefit from using thread-local instance
+ * recycling while different models of concurrency or versions of the JVM may benefit from using an object pooling
+ * strategy instead.
+ *
+ * @since 3.0.0
+ */
+@FunctionalInterface
+public interface RecyclerFactory {
+
+ /**
+ * Creates a new recycler using the given supplier function for initial instances. These instances have
+ * no cleaner function and are assumed to always be reusable.
+ *
+ * @param supplier function to provide new instances of a recyclable object
+ * @param the recyclable type
+ * @return a new recycler for V-type instances
+ */
+ default Recycler create(final Supplier supplier) {
+ return create(supplier, getDefaultCleaner());
+ }
+
+ /**
+ * Creates a new recycler using the given functions for providing fresh instances and for cleaning up
+ * existing instances for reuse. For example, a StringBuilder recycler would provide two functions:
+ * a supplier function for constructing a new StringBuilder with a preselected initial capacity and
+ * another function for trimming the StringBuilder to some preselected maximum capacity and setting
+ * its length back to 0 as if it were a fresh StringBuilder.
+ *
+ * @param supplier function to provide new instances of a recyclable object
+ * @param cleaner function to reset a recyclable object to a fresh state
+ * @param the recyclable type
+ * @return a new recycler for V-type instances
+ */
+ default Recycler create(Supplier supplier, Consumer cleaner) {
+ return create(supplier, cleaner, getDefaultCleaner());
+ }
+
+ /**
+ * Creates a new recycler using the given functions for providing fresh instances and for cleaning recycled
+ * instances lazily or eagerly. The lazy cleaner function is invoked on recycled instances before being
+ * returned by {@link Recycler#acquire()}. The eager cleaner function is invoked on recycled instances
+ * during {@link Recycler#release(Object)}.
+ *
+ * @param supplier function to provide new instances of a recyclable object
+ * @param lazyCleaner function to invoke to clean a recycled object before being acquired
+ * @param eagerCleaner function to invoke to clean a recycled object after being released
+ * @param the recyclable type
+ * @return a new recycler for V-type instances
+ */
+ Recycler create(Supplier supplier, Consumer lazyCleaner, Consumer eagerCleaner);
+
+ /**
+ * Creates a default cleaner function that does nothing.
+ */
+ static Consumer getDefaultCleaner() {
+ return ignored -> {};
+ }
+
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java
new file mode 100644
index 00000000000..8a031c1ed98
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java
@@ -0,0 +1,104 @@
+/*
+ * 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.spi;
+
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.util.Queues;
+
+/**
+ * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy
+ * may not be appropriate in workloads where units of work are independent of operating system threads such as
+ * reactive streams, coroutines, or virtual threads; a {@linkplain QueueingRecyclerFactory queue-based approach}
+ * is more flexible.
+ *
+ * @since 3.0.0
+ */
+public class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+ // This determines the maximum number of recyclable objects we may retain per thread.
+ // This allows us to acquire recyclable objects in recursive method calls and maintain
+ // minimal overhead in the scenarios where the active instance count goes far beyond this
+ // for a brief moment.
+ // Visible for testing
+ static final int MAX_QUEUE_SIZE = 8;
+
+ private static final ThreadLocalRecyclerFactory INSTANCE =
+ new ThreadLocalRecyclerFactory();
+
+ private ThreadLocalRecyclerFactory() {}
+
+ public static ThreadLocalRecyclerFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Recycler create(
+ final Supplier supplier,
+ final Consumer lazyCleaner,
+ final Consumer eagerCleaner) {
+ return new ThreadLocalRecycler<>(supplier, lazyCleaner, eagerCleaner);
+ }
+
+ // Visible for testing
+ static class ThreadLocalRecycler implements Recycler {
+
+ private final Supplier supplier;
+
+ private final Consumer lazyCleaner;
+
+ private final Consumer eagerCleaner;
+
+ private final ThreadLocal> holder;
+
+ private ThreadLocalRecycler(
+ final Supplier supplier,
+ final Consumer lazyCleaner,
+ final Consumer eagerCleaner) {
+ this.supplier = supplier;
+ this.lazyCleaner = lazyCleaner;
+ this.eagerCleaner = eagerCleaner;
+ this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(MAX_QUEUE_SIZE));
+ }
+
+ @Override
+ public V acquire() {
+ final Queue queue = holder.get();
+ final V value = queue.poll();
+ if (value == null) {
+ return supplier.get();
+ } else {
+ lazyCleaner.accept(value);
+ return value;
+ }
+ }
+
+ @Override
+ public void release(final V value) {
+ eagerCleaner.accept(value);
+ holder.get().offer(value);
+ }
+
+ // Visible for testing
+ Queue getQueue() {
+ return holder.get();
+ }
+
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
index 018d4a35a81..d25e3aedf62 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
@@ -18,15 +18,11 @@
import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.logging.log4j.Level;
@@ -38,6 +34,7 @@
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.LoggingSystemProperties;
import org.apache.logging.log4j.util.LowLevelLogUtil;
+import org.apache.logging.log4j.util.Queues;
/**
* Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}.
@@ -68,8 +65,6 @@ public final class StatusLogger extends AbstractLogger {
private final Queue messages;
- private final Lock msgLock = new ReentrantLock();
-
private int listenersLevel;
/**
@@ -102,7 +97,7 @@ public final class StatusLogger extends AbstractLogger {
this.logger = logger;
this.configuration = configuration;
this.listenersLevel = configuration.getDefaultLevel().intLevel();
- messages = new BoundedQueue<>(configuration.getMaxEntries());
+ messages = Queues.MPMC.create(configuration.getMaxEntries());
}
/**
@@ -178,6 +173,7 @@ public Iterable getListeners() {
* Clears the list of status events and listeners.
*/
public void reset() {
+ messages.clear();
listenersLock.writeLock().lock();
try {
for (final StatusListener listener : listeners) {
@@ -186,8 +182,6 @@ public void reset() {
} finally {
listeners.clear();
listenersLock.writeLock().unlock();
- // note this should certainly come after the unlock to avoid unnecessary nested locking
- clear();
}
}
@@ -205,24 +199,14 @@ private static void closeSilently(final Closeable resource) {
* @return The list of StatusData objects.
*/
public List getStatusData() {
- msgLock.lock();
- try {
- return new ArrayList<>(messages);
- } finally {
- msgLock.unlock();
- }
+ return List.copyOf(messages);
}
/**
* Clears the list of status events.
*/
public void clear() {
- msgLock.lock();
- try {
- messages.clear();
- } finally {
- msgLock.unlock();
- }
+ messages.clear();
}
@Override
@@ -247,11 +231,9 @@ public void logMessage(final String fqcn, final Level level, final Marker marker
element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
}
final StatusData data = new StatusData(element, level, msg, t, null);
- msgLock.lock();
- try {
- messages.add(data);
- } finally {
- msgLock.unlock();
+ while (!messages.offer(data)) {
+ // discard an old message and retry
+ messages.poll();
}
// LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled
if (configuration.isDebugEnabled() || listeners.isEmpty()) {
@@ -393,28 +375,4 @@ public boolean isEnabled(final Level level, final Marker marker) {
return logger.isEnabled(level, marker);
}
- /**
- * Queues for status events.
- *
- * @param Object type to be stored in the queue.
- */
- private class BoundedQueue extends ConcurrentLinkedQueue {
-
- private static final long serialVersionUID = -3945953719763255337L;
-
- private final int size;
-
- BoundedQueue(final int size) {
- this.size = size;
- }
-
- @Override
- public boolean add(final E object) {
- super.add(object);
- while (messages.size() > size) {
- messages.poll();
- }
- return size > 0;
- }
- }
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java
similarity index 85%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java
index 6e6924b366b..53a6d6db29e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java
@@ -14,12 +14,10 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.util;
+package org.apache.logging.log4j.util;
-public interface Recycler {
-
- V acquire();
-
- void release(V value);
+import java.util.Queue;
+public interface QueueFactory {
+ Queue create();
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java
new file mode 100644
index 00000000000..2f0167f2ea9
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java
@@ -0,0 +1,198 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import org.jctools.queues.MpmcArrayQueue;
+import org.jctools.queues.MpscArrayQueue;
+import org.jctools.queues.SpmcArrayQueue;
+import org.jctools.queues.SpscArrayQueue;
+
+/**
+ * Provides {@link QueueFactory} and {@link Queue} instances for different use cases. When the
+ * JCTools library is included at runtime, then
+ * the specialized lock free or wait free queues are used from there. Otherwise, {@link ArrayBlockingQueue}
+ * is provided as a fallback for thread-safety. Custom implementations of {@link QueueFactory} may also be
+ * created via {@link #createQueueFactory(String, int)}.
+ */
+@InternalApi
+public enum Queues {
+ /**
+ * Provides a bounded queue for single-producer/single-consumer usage. Only one thread may offer objects
+ * while only one thread may poll for them.
+ */
+ SPSC(Lazy.lazy(JCToolsQueueFactory.SPSC::load)),
+ /**
+ * Provides a bounded queue for multi-producer/single-consumer usage. Any thread may offer objects while only
+ * one thread may poll for them.
+ */
+ MPSC(Lazy.lazy(JCToolsQueueFactory.MPSC::load)),
+ /**
+ * Provides a bounded queue for single-producer/multi-consumer usage. Only one thread may offer objects but
+ * any thread may poll for them.
+ */
+ SPMC(Lazy.lazy(JCToolsQueueFactory.SPMC::load)),
+ /**
+ * Provides a bounded queue for multi-producer/multi-consumer usage. Any thread may offer objects and any thread
+ * may poll for them.
+ */
+ MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load));
+
+ private final Lazy queueFactory;
+
+ Queues(final Lazy queueFactory) {
+ this.queueFactory = queueFactory;
+ }
+
+ public QueueFactory factory(final int capacity) {
+ return new ProxyQueueFactory(queueFactory.get(), capacity);
+ }
+
+ public Queue create(final int capacity) {
+ return queueFactory.get().create(capacity);
+ }
+
+ public static QueueFactory createQueueFactory(final String supplierPath, final int capacity) {
+ final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.');
+ if (supplierPathSplitterIndex < 0) {
+ throw new IllegalArgumentException("invalid supplier in queue factory: " + supplierPath);
+ }
+ final String supplierClassName = supplierPath.substring(0, supplierPathSplitterIndex);
+ final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1);
+ try {
+ final Class> supplierClass = LoaderUtil.loadClass(supplierClassName);
+ final BoundedQueueFactory queueFactory;
+ if ("new".equals(supplierMethodName)) {
+ final Constructor> supplierCtor =
+ supplierClass.getDeclaredConstructor(int.class);
+ queueFactory = new ConstructorProvidedQueueFactory(supplierCtor);
+ } else {
+ final Method supplierMethod =
+ supplierClass.getMethod(supplierMethodName, int.class);
+ queueFactory = new StaticMethodProvidedQueueFactory(supplierMethod);
+ }
+ return new ProxyQueueFactory(queueFactory, capacity);
+ } catch (final ReflectiveOperationException | LinkageError | SecurityException error) {
+ throw new RuntimeException("failed executing queue factory", error);
+ }
+ }
+
+ private static class ProxyQueueFactory implements QueueFactory {
+ private final BoundedQueueFactory factory;
+ private final int capacity;
+
+ private ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) {
+ this.factory = factory;
+ this.capacity = capacity;
+ }
+
+ @Override
+ public Queue create() {
+ return factory.create(capacity);
+ }
+ }
+
+ private interface BoundedQueueFactory {
+ Queue create(final int capacity);
+ }
+
+ private static class ArrayBlockingQueueFactory implements BoundedQueueFactory {
+ @Override
+ public Queue create(final int capacity) {
+ return new ArrayBlockingQueue<>(capacity);
+ }
+ }
+
+ private enum JCToolsQueueFactory implements BoundedQueueFactory {
+ SPSC {
+ @Override
+ public Queue create(final int capacity) {
+ return new SpscArrayQueue<>(capacity);
+ }
+ },
+ MPSC {
+ @Override
+ public Queue create(final int capacity) {
+ return new MpscArrayQueue<>(capacity);
+ }
+ },
+ SPMC {
+ @Override
+ public Queue create(final int capacity) {
+ return new SpmcArrayQueue<>(capacity);
+ }
+ },
+ MPMC {
+ @Override
+ public Queue create(final int capacity) {
+ return new MpmcArrayQueue<>(capacity);
+ }
+ };
+
+ BoundedQueueFactory load() {
+ try {
+ // if JCTools is unavailable at runtime, then we'll only find out once we attempt to invoke
+ // BoundedQueueFactory::create which is the first time the ClassLoader will try to link the
+ // referenced JCTools class causing a NoClassDefFoundError or some other LinkageError potentially.
+ // also, test with a large enough capacity to avoid any IllegalArgumentExceptions from trivial queues
+ create(16);
+ return this;
+ } catch (final LinkageError ignored) {
+ return new ArrayBlockingQueueFactory();
+ }
+ }
+ }
+
+ private static class ConstructorProvidedQueueFactory implements BoundedQueueFactory {
+ private final Constructor> constructor;
+
+ private ConstructorProvidedQueueFactory(final Constructor> constructor) {
+ this.constructor = constructor;
+ }
+
+ @Override
+ public Queue create(final int capacity) {
+ final Constructor> typedConstructor = Cast.cast(constructor);
+ try {
+ return typedConstructor.newInstance(capacity);
+ } catch (final ReflectiveOperationException e) {
+ throw new RuntimeException("queue construction failed for factory", e);
+ }
+ }
+ }
+
+ private static class StaticMethodProvidedQueueFactory implements BoundedQueueFactory {
+ private final Method method;
+
+ private StaticMethodProvidedQueueFactory(final Method method) {
+ this.method = method;
+ }
+
+ @Override
+ public Queue create(final int capacity) {
+ try {
+ return Cast.cast(method.invoke(null, capacity));
+ } catch (final ReflectiveOperationException e) {
+ throw new RuntimeException("queue construction failed for factory", e);
+ }
+ }
+ }
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java
similarity index 99%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java
index c2333a588eb..653877c1f49 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java
@@ -14,9 +14,7 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.util;
-
-import org.apache.logging.log4j.util.Strings;
+package org.apache.logging.log4j.util;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -25,6 +23,8 @@
import java.util.Set;
import java.util.concurrent.Callable;
+import org.apache.logging.log4j.util.Strings;
+
public final class StringParameterParser {
private StringParameterParser() {}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
index afb38179f50..8ed89f3f697 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
@@ -16,8 +16,6 @@
*/
package org.apache.logging.log4j.core;
-import com.google.common.io.ByteStreams;
-
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
@@ -28,26 +26,23 @@
import static org.junit.Assert.assertTrue;
public final class GarbageCollectionHelper implements Closeable, Runnable {
- private static final OutputStream sink = ByteStreams.nullOutputStream();
+ private static final OutputStream sink = OutputStream.nullOutputStream();
private final AtomicBoolean running = new AtomicBoolean();
private final CountDownLatch latch = new CountDownLatch(1);
- private final Thread gcThread = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- while (running.get()) {
- // Allocate data to help suggest a GC
- try {
- // 1mb of heap
- sink.write(new byte[1024 * 1024]);
- } catch (IOException ignored) {
- }
- // May no-op depending on the jvm configuration
- System.gc();
+ private final Thread gcThread = new Thread(() -> {
+ try {
+ while (running.get()) {
+ // Allocate data to help suggest a GC
+ try {
+ // 1mb of heap
+ sink.write(new byte[1024 * 1024]);
+ } catch (IOException ignored) {
}
- } finally {
- latch.countDown();
+ // May no-op depending on the jvm configuration
+ System.gc();
}
+ } finally {
+ latch.countDown();
}
});
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 def7ef13472..3ddbadcc69e 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
@@ -192,7 +192,7 @@ public void testCreateMementoRetainsParametersAndFormat() {
assertArrayEquals(new String[]{"World"}, actual.getParameters());
assertEquals("Hello World!", actual.getFormattedMessage());
} finally {
- ReusableMessageFactory.release(message);
+ factory.recycle(message);
}
}
@@ -220,7 +220,7 @@ public void testMementoReuse() {
final Message memento2 = evt.memento();
assertThat(memento1, sameInstance(memento2));
} finally {
- ReusableMessageFactory.release(message);
+ factory.recycle(message);
}
}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
index ad44828a541..4769cf652a5 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
@@ -33,46 +33,45 @@
public class ReusableLogEventFactoryTest {
private final Injector injector = DI.createInjector();
+ private ReusableLogEventFactory factory;
@BeforeEach
void setUp() {
injector.init();
+ factory = injector.getInstance(ReusableLogEventFactory.class);
}
@Test
public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception {
- final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class);
- final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
- final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
+ final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null);
+ final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null);
assertNotSame(event1, event2);
- ReusableLogEventFactory.release(event1);
- ReusableLogEventFactory.release(event2);
+ factory.recycle(event1);
+ factory.recycle(event2);
}
@Test
public void testCreateEventReturnsSameInstance() throws Exception {
- final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class);
- final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
- ReusableLogEventFactory.release(event1);
- final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
+ final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null);
+ factory.recycle(event1);
+ final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null);
assertSame(event1, event2);
- ReusableLogEventFactory.release(event2);
- final LogEvent event3 = callCreateEvent(factory, "c", Level.INFO, new SimpleMessage("123"), null);
+ factory.recycle(event2);
+ final LogEvent event3 = callCreateEvent("c", Level.INFO, new SimpleMessage("123"), null);
assertSame(event2, event3);
- ReusableLogEventFactory.release(event3);
+ factory.recycle(event3);
}
@Test
public void testCreateEventOverwritesFields() throws Exception {
- final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class);
- final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
+ final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null);
assertEquals("a", event1.getLoggerName(), "logger");
assertEquals(Level.DEBUG, event1.getLevel(), "level");
assertEquals(new SimpleMessage("abc"), event1.getMessage(), "msg");
- ReusableLogEventFactory.release(event1);
- final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
+ factory.recycle(event1);
+ final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null);
assertSame(event1, event2);
assertEquals("b", event1.getLoggerName(), "logger");
@@ -83,26 +82,24 @@ public void testCreateEventOverwritesFields() throws Exception {
assertEquals(new SimpleMessage("xyz"), event2.getMessage(), "msg");
}
- private LogEvent callCreateEvent(final ReusableLogEventFactory factory, final String logger, final Level level,
- final Message message, final Throwable thrown) {
+ private LogEvent callCreateEvent(final String logger, final Level level, final Message message, final Throwable thrown) {
return factory.createEvent(logger, null, getClass().getName(), level, message, null, thrown);
}
@Test
public void testCreateEventReturnsThreadLocalInstance() throws Exception {
- final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class);
final LogEvent[] event1 = new LogEvent[1];
final LogEvent[] event2 = new LogEvent[1];
final Thread t1 = new Thread("THREAD 1") {
@Override
public void run() {
- event1[0] = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
+ event1[0] = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null);
}
};
final Thread t2 = new Thread("Thread 2") {
@Override
public void run() {
- event2[0] = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
+ event2[0] = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null);
}
};
t1.start();
@@ -123,19 +120,18 @@ public void run() {
assertEquals(new SimpleMessage("xyz"), event2[0].getMessage(), "msg");
assertEquals("Thread 2", event2[0].getThreadName(), "thread name");
assertEquals(t2.getId(), event2[0].getThreadId(), "tid");
- ReusableLogEventFactory.release(event1[0]);
- ReusableLogEventFactory.release(event2[0]);
+ factory.recycle(event1[0]);
+ factory.recycle(event2[0]);
}
@Test
public void testCreateEventInitFieldsProperly() throws Exception {
- final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class);
- final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null);
+ final LogEvent event = callCreateEvent("logger", Level.INFO, new SimpleMessage("xyz"), null);
try {
assertNotNull(event.getContextData());
assertNotNull(event.getContextStack());
} finally {
- ReusableLogEventFactory.release(event);
+ factory.recycle(event);
}
}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
index 65855348f3d..257a291b167 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
@@ -1,4 +1,4 @@
-package org.apache.logging.log4j.core.layout;/*
+/*
* 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.
@@ -14,13 +14,15 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
+package org.apache.logging.log4j.core.layout;
import java.nio.charset.Charset;
import org.apache.logging.log4j.core.LogEvent;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests AbstractStringLayout.
@@ -34,10 +36,6 @@ public ConcreteStringLayout() {
super(Charset.defaultCharset());
}
- public static StringBuilder getStringBuilder() {
- return AbstractStringLayout.getStringBuilder();
- }
-
@Override
public String toSerializable(final LogEvent event) {
return null;
@@ -46,7 +44,8 @@ public String toSerializable(final LogEvent event) {
@Test
public void testGetStringBuilderCapacityRestrictedToMax() throws Exception {
- final StringBuilder sb = ConcreteStringLayout.getStringBuilder();
+ final ConcreteStringLayout layout = new ConcreteStringLayout();
+ final StringBuilder sb = layout.acquireStringBuilder();
final int initialCapacity = sb.capacity();
assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity");
@@ -55,8 +54,9 @@ public void testGetStringBuilderCapacityRestrictedToMax() throws Exception {
sb.append(smallMessage);
assertEquals(initialCapacity, sb.capacity(), "capacity not grown");
assertEquals(SMALL, sb.length(), "length=msg length");
+ layout.releaseStringBuilder(sb);
- final StringBuilder sb2 = ConcreteStringLayout.getStringBuilder();
+ final StringBuilder sb2 = layout.acquireStringBuilder();
assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged");
assertEquals(0, sb2.length(), "empty, ready for use");
@@ -70,8 +70,9 @@ public void testGetStringBuilderCapacityRestrictedToMax() throws Exception {
sb2.setLength(0); // set 0 before next getStringBuilder() call
assertEquals(0, sb2.length(), "empty, cleared");
assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, "capacity remains very large");
+ layout.releaseStringBuilder(sb2);
- final StringBuilder sb3 = ConcreteStringLayout.getStringBuilder();
+ final StringBuilder sb3 = layout.acquireStringBuilder();
assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(),
"capacity, trimmed to MAX_STRING_BUILDER_SIZE");
assertEquals(0, sb3.length(), "empty, ready for use");
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
new file mode 100644
index 00000000000..d2209164c95
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+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;
+
+public interface ReusableLogEvent extends LogEvent {
+ /**
+ * Clears all references this event has to other objects.
+ */
+ void clear();
+
+ void setLoggerFqcn(final String loggerFqcn);
+
+ void setMarker(final Marker marker);
+
+ void setLevel(final Level level);
+
+ void setLoggerName(final String loggerName);
+
+ void setMessage(final Message message);
+
+ void setThrown(final Throwable thrown);
+
+ void setTimeMillis(final long timeMillis);
+
+ void setInstant(final Instant instant);
+
+ void setSource(final StackTraceElement source);
+
+ @Override
+ StringMap getContextData();
+
+ void setContextData(final StringMap mutableContextData);
+
+ void setContextStack(final ThreadContext.ContextStack contextStack);
+
+ void setThreadId(final long threadId);
+
+ void setThreadName(final String threadName);
+
+ 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/appender/AsyncAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java
index e19a2a45b0c..ae2b2c6582e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java
@@ -75,9 +75,9 @@ public final class AsyncAppender extends AbstractAppender {
private AsyncQueueFullPolicy asyncQueueFullPolicy;
private AsyncAppender(final String name, final Filter filter, final AppenderRef[] appenderRefs,
- final String errorRef, final int queueSize, final boolean blocking, final boolean ignoreExceptions,
- final long shutdownTimeout, final Configuration config, final boolean includeLocation,
- final BlockingQueueFactory blockingQueueFactory, final Property[] properties) {
+ final String errorRef, final int queueSize, final boolean blocking, final boolean ignoreExceptions,
+ final long shutdownTimeout, final Configuration config, final boolean includeLocation,
+ final BlockingQueueFactory blockingQueueFactory, final Property[] properties) {
super(name, filter, null, ignoreExceptions, properties);
this.queue = blockingQueueFactory.create(queueSize);
this.queueSize = queueSize;
@@ -273,7 +273,7 @@ public static class Builder> extends AbstractFilterable.Bui
private boolean ignoreExceptions = true;
@PluginElement(BlockingQueueFactory.ELEMENT_TYPE)
- private BlockingQueueFactory blockingQueueFactory = new ArrayBlockingQueueFactory<>();
+ private BlockingQueueFactory blockingQueueFactory = new ArrayBlockingQueueFactory();
public Builder setAppenderRefs(final AppenderRef[] appenderRefs) {
this.appenderRefs = appenderRefs;
@@ -320,7 +320,7 @@ public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
return this;
}
- public Builder setBlockingQueueFactory(final BlockingQueueFactory blockingQueueFactory) {
+ public Builder setBlockingQueueFactory(final BlockingQueueFactory blockingQueueFactory) {
this.blockingQueueFactory = blockingQueueFactory;
return this;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java
index 3126a65e76e..ed495dd55c1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java
@@ -16,13 +16,13 @@
*/
package org.apache.logging.log4j.core.async;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginFactory;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-
/**
* Factory for creating instances of {@link ArrayBlockingQueue}.
*
@@ -30,14 +30,14 @@
*/
@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE)
@Plugin("ArrayBlockingQueue")
-public class ArrayBlockingQueueFactory implements BlockingQueueFactory {
+public class ArrayBlockingQueueFactory implements BlockingQueueFactory {
@Override
- public BlockingQueue create(final int capacity) {
+ public BlockingQueue create(final int capacity) {
return new ArrayBlockingQueue<>(capacity);
}
@PluginFactory
- public static ArrayBlockingQueueFactory createFactory() {
- return new ArrayBlockingQueueFactory<>();
+ public static ArrayBlockingQueueFactory createFactory() {
+ return new ArrayBlockingQueueFactory();
}
}
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 e314e6cf9c5..1796931add0 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
@@ -37,6 +37,7 @@
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.StringMap;
@@ -418,7 +419,7 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin
event.setValues(asyncLogger, asyncLogger.getName(), marker, fqcn, level, message, thrown,
// config properties are taken care of in the EventHandler thread
// in the AsyncLogger#actualAsyncLog method
- contextDataInjector.injectContextData(null, (StringMap) event.getContextData()),
+ contextDataInjector.injectContextData(null, event.getContextData()),
contextStack, currentThread.getId(), threadName, currentThread.getPriority(), location,
clock, nanoClock);
}
@@ -492,24 +493,24 @@ public void actualAsyncLog(final RingBufferLogEvent event) {
privateConfigLoggerConfig.getReliabilityStrategy().log(this, event);
}
- @SuppressWarnings("ForLoopReplaceableByForEach") // Avoid iterator allocation
+ @PerformanceSensitive("allocation")
private void onPropertiesPresent(final RingBufferLogEvent event, final List properties) {
final StringMap contextData = getContextData(event);
- for (int i = 0, size = properties.size(); i < size; i++) {
- final Property prop = properties.get(i);
+ // List::forEach is garbage-free when using an ArrayList or Arrays.asList
+ properties.forEach(prop -> {
if (contextData.getValue(prop.getName()) != null) {
- continue; // contextMap overrides config properties
+ return; // contextMap overrides config properties
}
final String value = prop.isValueNeedsLookup() //
? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) //
: prop.getValue();
contextData.putValue(prop.getName(), value);
- }
+ });
event.setContextData(contextData);
}
private static StringMap getContextData(final RingBufferLogEvent event) {
- final StringMap contextData = (StringMap) event.getContextData();
+ final StringMap contextData = event.getContextData();
if (contextData.isFrozen()) {
final StringMap temp = ContextDataFactory.createContextData();
temp.putAll(contextData);
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 7593f7fb520..15b1eea9bdb 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
@@ -16,6 +16,10 @@
*/
package org.apache.logging.log4j.core.async;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
@@ -33,10 +37,6 @@
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.Strings;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
/**
* Asynchronous Logger object that is created via configuration and can be
* combined with synchronous loggers.
@@ -255,7 +255,7 @@ public static LoggerConfig createLogger(
return new AsyncLoggerConfig(name, appenderRefs, filter, level,
additive, properties, config, includeLocation(includeLocation),
- config.getComponent(LogEventFactory.KEY));
+ config.getLogEventFactory());
}
/**
@@ -278,7 +278,7 @@ public static LoggerConfig createLogger(
final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) {
final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
return new AsyncLoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config,
- includeLocation(includeLocation), config.getComponent(LogEventFactory.KEY));
+ includeLocation(includeLocation), config.getLogEventFactory());
}
// Note: for asynchronous loggers, includeLocation default is FALSE
@@ -318,7 +318,7 @@ public static LoggerConfig createLogger(final String additivity, final Level lev
final boolean additive = Booleans.parseBoolean(additivity, true);
return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive,
properties, config, AsyncLoggerConfig.includeLocation(includeLocation),
- config.getComponent(LogEventFactory.KEY));
+ config.getLogEventFactory());
}
}
}
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 b689de0d4cf..337c27d8e3a 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
@@ -22,6 +22,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.ReusableLogEvent;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.impl.Log4jProperties;
import org.apache.logging.log4j.core.impl.LogEventFactory;
@@ -80,8 +81,8 @@ public Log4jEventWrapper(final MutableLogEvent mutableLogEvent) {
*/
public void clear() {
loggerConfig = null;
- if (event instanceof MutableLogEvent) {
- ((MutableLogEvent) event).clear();
+ if (event instanceof ReusableLogEvent) {
+ ((ReusableLogEvent) event).clear();
} else {
event = null;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java
index 8f0e7b745e2..2ae6a27ae38 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java
@@ -23,7 +23,7 @@
*
* @since 2.7
*/
-public interface BlockingQueueFactory {
+public interface BlockingQueueFactory {
/**
* The {@link org.apache.logging.log4j.plugins.Configurable#elementType() element type} to use for plugins
@@ -38,5 +38,5 @@ public interface BlockingQueueFactory {
* @param capacity maximum size of the queue if supported
* @return a new BlockingQueue
*/
- BlockingQueue create(int capacity);
+ BlockingQueue create(int capacity);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java
index 39bc3ccda3c..69fb710609c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java
@@ -16,14 +16,15 @@
*/
package org.apache.logging.log4j.core.async;
-import com.conversantmedia.util.concurrent.DisruptorBlockingQueue;
-import com.conversantmedia.util.concurrent.SpinPolicy;
+import java.util.concurrent.BlockingQueue;
+
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.plugins.PluginFactory;
-import java.util.concurrent.BlockingQueue;
+import com.conversantmedia.util.concurrent.DisruptorBlockingQueue;
+import com.conversantmedia.util.concurrent.SpinPolicy;
/**
* Factory for creating instances of {@link DisruptorBlockingQueue}.
@@ -32,7 +33,7 @@
*/
@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true)
@Plugin("DisruptorBlockingQueue")
-public class DisruptorBlockingQueueFactory implements BlockingQueueFactory {
+public class DisruptorBlockingQueueFactory implements BlockingQueueFactory {
private final SpinPolicy spinPolicy;
@@ -41,14 +42,14 @@ private DisruptorBlockingQueueFactory(final SpinPolicy spinPolicy) {
}
@Override
- public BlockingQueue create(final int capacity) {
+ public BlockingQueue create(final int capacity) {
return new DisruptorBlockingQueue<>(capacity, spinPolicy);
}
@PluginFactory
- public static DisruptorBlockingQueueFactory createFactory(
+ public static DisruptorBlockingQueueFactory createFactory(
@PluginAttribute(defaultString = "WAITING") final SpinPolicy spinPolicy
) {
- return new DisruptorBlockingQueueFactory<>(spinPolicy);
+ return new DisruptorBlockingQueueFactory(spinPolicy);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java
index 1e304dd8a58..c6df4e02e85 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java
@@ -16,17 +16,17 @@
*/
package org.apache.logging.log4j.core.async;
+import java.util.Collection;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.plugins.PluginFactory;
import org.jctools.queues.MpscArrayQueue;
-import java.util.Collection;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.LockSupport;
-
/**
* Factory for creating instances of BlockingQueues backed by JCTools {@link MpscArrayQueue}.
*
@@ -34,7 +34,7 @@
*/
@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true)
@Plugin("JCToolsBlockingQueue")
-public class JCToolsBlockingQueueFactory implements BlockingQueueFactory {
+public class JCToolsBlockingQueueFactory implements BlockingQueueFactory {
private final WaitStrategy waitStrategy;
@@ -43,14 +43,14 @@ private JCToolsBlockingQueueFactory(final WaitStrategy waitStrategy) {
}
@Override
- public BlockingQueue create(final int capacity) {
+ public BlockingQueue create(final int capacity) {
return new MpscBlockingQueue<>(capacity, waitStrategy);
}
@PluginFactory
- public static JCToolsBlockingQueueFactory createFactory(
+ public static JCToolsBlockingQueueFactory createFactory(
@PluginAttribute(defaultString = "PARK") final WaitStrategy waitStrategy) {
- return new JCToolsBlockingQueueFactory<>(waitStrategy);
+ return new JCToolsBlockingQueueFactory(waitStrategy);
}
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java
index d5dfa11e88e..46f4b3e8951 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java
@@ -14,16 +14,15 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.core.async;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedTransferQueue;
+
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginFactory;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedTransferQueue;
-
/**
* Factory for creating instances of {@link LinkedTransferQueue}.
*
@@ -31,14 +30,14 @@
*/
@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true)
@Plugin("LinkedTransferQueue")
-public class LinkedTransferQueueFactory implements BlockingQueueFactory {
+public class LinkedTransferQueueFactory implements BlockingQueueFactory {
@Override
- public BlockingQueue create(final int capacity) {
+ public BlockingQueue create(final int capacity) {
return new LinkedTransferQueue<>();
}
@PluginFactory
- public static LinkedTransferQueueFactory createFactory() {
- return new LinkedTransferQueueFactory<>();
+ public static LinkedTransferQueueFactory createFactory() {
+ return new LinkedTransferQueueFactory();
}
}
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 7904daaa0c3..02813f0234e 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
@@ -22,6 +22,7 @@
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.ReusableLogEvent;
import org.apache.logging.log4j.core.impl.ContextDataFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.impl.MementoMessage;
@@ -37,7 +38,6 @@
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.StringMap;
import org.apache.logging.log4j.util.Strings;
@@ -50,7 +50,7 @@
* When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during
* the life of the RingBuffer.
*/
-public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence, ParameterVisitable {
+public class RingBufferLogEvent implements ReusableLogEvent, ReusableMessage, CharSequence, ParameterVisitable {
/** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
public static final EventFactory FACTORY = new Factory();
@@ -83,7 +83,7 @@ public RingBufferLogEvent newInstance() {
private String messageFormat;
private StringBuilder messageText;
private Object[] parameters;
- private transient Throwable thrown;
+ private Throwable thrown;
private ThrowableProxy thrownProxy;
private StringMap contextData = ContextDataFactory.createContextData();
private Marker marker;
@@ -91,7 +91,7 @@ public RingBufferLogEvent newInstance() {
private StackTraceElement location;
private ContextStack contextStack;
- private transient AsyncLogger asyncLogger;
+ private AsyncLogger asyncLogger;
public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker,
final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable,
@@ -130,7 +130,8 @@ public LogEvent toImmutable() {
return toMemento();
}
- private void setMessage(final Message msg) {
+ @Override
+ public void setMessage(final Message msg) {
if (msg instanceof ReusableMessage) {
final ReusableMessage reusable = (ReusableMessage) msg;
reusable.formatTo(getMessageTextForWriting());
@@ -199,16 +200,31 @@ public String getLoggerName() {
return loggerName;
}
+ @Override
+ public void setLoggerName(final String loggerName) {
+ this.loggerName = loggerName;
+ }
+
@Override
public Marker getMarker() {
return marker;
}
+ @Override
+ public void setMarker(final Marker marker) {
+ this.marker = marker;
+ }
+
@Override
public String getLoggerFqcn() {
return fqcn;
}
+ @Override
+ public void setLoggerFqcn(final String loggerFqcn) {
+ fqcn = loggerFqcn;
+ }
+
@Override
public Level getLevel() {
if (level == null) {
@@ -217,6 +233,11 @@ public Level getLevel() {
return level;
}
+ @Override
+ public void setLevel(final Level level) {
+ this.level = level;
+ }
+
@Override
public Message getMessage() {
if (message == null) {
@@ -333,6 +354,11 @@ public Throwable getThrown() {
return thrown;
}
+ @Override
+ public void setThrown(final Throwable thrown) {
+ this.thrown = thrown;
+ }
+
@Override
public ThrowableProxy getThrownProxy() {
// lazily instantiate the (expensive) ThrowableProxy
@@ -345,11 +371,12 @@ public ThrowableProxy getThrownProxy() {
}
@Override
- public ReadOnlyStringMap getContextData() {
+ public StringMap getContextData() {
return contextData;
}
- void setContextData(final StringMap contextData) {
+ @Override
+ public void setContextData(final StringMap contextData) {
this.contextData = contextData;
}
@@ -358,44 +385,85 @@ public ContextStack getContextStack() {
return contextStack;
}
+ @Override
+ public void setContextStack(final ContextStack contextStack) {
+ this.contextStack = contextStack;
+ }
+
@Override
public long getThreadId() {
return threadId;
}
+ @Override
+ public void setThreadId(final long threadId) {
+ this.threadId = threadId;
+ }
+
@Override
public String getThreadName() {
return threadName;
}
+ @Override
+ public void setThreadName(final String threadName) {
+ this.threadName = threadName;
+ }
+
@Override
public int getThreadPriority() {
return threadPriority;
}
+ @Override
+ public void setThreadPriority(final int threadPriority) {
+ this.threadPriority = threadPriority;
+ }
+
@Override
public StackTraceElement getSource() {
return location;
}
+ @Override
+ public void setSource(final StackTraceElement source) {
+ location = source;
+ }
+
@Override
public long getTimeMillis() {
return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : instant.getEpochMillisecond();
}
+ @Override
+ public void setTimeMillis(final long timeMillis) {
+ instant.initFromEpochMilli(timeMillis, 0);
+ }
+
@Override
public Instant getInstant() {
return instant;
}
+ @Override
+ public void setInstant(final Instant instant) {
+ this.instant.initFrom(instant);
+ }
+
@Override
public long getNanoTime() {
return nanoTime;
}
+ @Override
+ public void setNanoTime(final long nanoTime) {
+ this.nanoTime = nanoTime;
+ }
+
/**
* Release references held by ring buffer to allow objects to be garbage-collected.
*/
+ @Override
public void clear() {
this.populated = false;
@@ -438,6 +506,7 @@ public void clear() {
* 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) {
builder.setContextData(contextData) //
.setContextStack(contextStack) //
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
index 53fa9a4f94f..f9382bc8a28 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
@@ -16,6 +16,10 @@
*/
package org.apache.logging.log4j.core.config;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
@@ -25,6 +29,7 @@
import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
import org.apache.logging.log4j.core.async.AsyncWaitStrategyFactory;
import org.apache.logging.log4j.core.filter.Filterable;
+import org.apache.logging.log4j.core.impl.LogEventFactory;
import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.net.Advertiser;
@@ -33,10 +38,7 @@
import org.apache.logging.log4j.core.util.WatchManager;
import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.plugins.di.Key;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.RecyclerFactory;
/**
* Interface that must be implemented to create a configuration.
@@ -238,4 +240,12 @@ default T getComponent(Key key) {
* @return the logger context.
*/
LoggerContext getLoggerContext();
+
+ default LogEventFactory getLogEventFactory() {
+ return getComponent(LogEventFactory.KEY);
+ }
+
+ default RecyclerFactory getRecyclerFactory() {
+ return getComponent(Key.forClass(RecyclerFactory.class));
+ }
}
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 aeaf73c00b7..5cf962ba26c 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
@@ -37,7 +37,6 @@
import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
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;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.message.Message;
@@ -253,7 +252,8 @@ protected LoggerConfig(
this.includeLocation = includeLocation;
this.config = config;
if (properties != null && properties.length > 0) {
- this.properties = List.of(properties.clone());
+ // don't use List.of() here as that will create temporary iterators downstream
+ this.properties = Arrays.asList(properties.clone());
} else {
this.properties = null;
}
@@ -478,7 +478,7 @@ public void log(final String loggerName, final String fqcn, final Marker marker,
log(logEvent, LoggerConfigPredicate.ALL);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
- ReusableLogEventFactory.release(logEvent);
+ logEventFactory.recycle(logEvent);
}
}
@@ -507,7 +507,7 @@ public void log(final String loggerName, final String fqcn, final StackTraceElem
log(logEvent, LoggerConfigPredicate.ALL);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
- ReusableLogEventFactory.release(logEvent);
+ logEventFactory.recycle(logEvent);
}
}
@@ -656,7 +656,7 @@ public static LoggerConfig createLogger(
final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) {
final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config,
- includeLocation(includeLocation, config), config.getComponent(LogEventFactory.KEY));
+ includeLocation(includeLocation, config), config.getLogEventFactory());
}
// Note: for asynchronous loggers, includeLocation default is FALSE,
@@ -817,7 +817,7 @@ public static LoggerConfig createLogger(
return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive,
properties, config, includeLocation(includeLocation, config),
- config.getComponent(LogEventFactory.KEY));
+ config.getLogEventFactory());
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
index 6e5f4f8168c..094cdfb679c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
@@ -53,7 +53,9 @@
import org.apache.logging.log4j.plugins.di.Injector;
import org.apache.logging.log4j.spi.CopyOnWrite;
import org.apache.logging.log4j.spi.DefaultThreadContextMap;
+import org.apache.logging.log4j.spi.LoggingSystem;
import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
+import org.apache.logging.log4j.spi.RecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.PropertyEnvironment;
@@ -88,6 +90,11 @@ public DefaultBundle(final Injector injector, final PropertyEnvironment properti
this.classLoader = classLoader;
}
+ @SingletonFactory
+ public RecyclerFactory defaultRecyclerFactory() {
+ return LoggingSystem.getRecyclerFactory();
+ }
+
@ConditionalOnProperty(name = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME)
@SingletonFactory
@Ordered(100)
@@ -202,8 +209,8 @@ public LogEventFactory systemPropertyLogEventFactory() throws ClassNotFoundExcep
@SingletonFactory
public LogEventFactory defaultLogEventFactory(
- final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) {
- return isThreadLocalsEnabled() ? new ReusableLogEventFactory(injector, clock, nanoClock) :
+ final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock, final RecyclerFactory recyclerFactory) {
+ return isThreadLocalsEnabled() ? new ReusableLogEventFactory(injector, clock, nanoClock, recyclerFactory) :
new DefaultLogEventFactory(injector, clock, nanoClock);
}
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 a0ab9354543..206a8924c59 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
@@ -23,6 +23,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.RingBufferLogEvent;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.time.Clock;
@@ -91,12 +92,8 @@ public Builder() {
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);
+ if (other instanceof ReusableLogEvent) {
+ ((ReusableLogEvent) other).initializeBuilder(this);
return;
}
this.loggerFqcn = other.getLoggerFqcn();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
index 476396d8414..86a29166b5c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
@@ -14,18 +14,18 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.core.impl;
+import java.util.List;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.ReusableLogEvent;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.plugins.di.Key;
-import java.util.List;
-
/**
*
*/
@@ -46,4 +46,10 @@ default LogEvent createEvent(
Throwable t) {
return createEvent(loggerName, marker, fqcn, level, data, properties, t);
}
+
+ default void recycle(final LogEvent event) {
+ if (event instanceof ReusableLogEvent) {
+ ((ReusableLogEvent) event).clear();
+ }
+ }
}
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 75eb5fb7f54..a009dd201ba 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -22,6 +22,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.ReusableLogEvent;
import org.apache.logging.log4j.core.async.InternalAsyncUtil;
import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.Instant;
@@ -34,17 +35,17 @@
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.StringMap;
import org.apache.logging.log4j.util.Strings;
/**
- * Mutable implementation of the {@code LogEvent} interface.
+ * Mutable implementation of the {@code ReusableLogEvent} interface.
* @since 2.6
+ * @see org.apache.logging.log4j.spi.Recycler
*/
-public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisitable {
+public class MutableLogEvent implements ReusableLogEvent, ReusableMessage, ParameterVisitable {
private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
private int threadPriority;
@@ -68,7 +69,6 @@ public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisi
private String loggerFqcn;
private StackTraceElement source;
private ThreadContext.ContextStack contextStack;
- transient boolean reserved = false;
public MutableLogEvent() {
// messageText and the parameter array are lazily initialized
@@ -86,9 +86,9 @@ public LogEvent toImmutable() {
}
/**
- * Initialize the fields of this {@code MutableLogEvent} from another event.
+ * Initialize the fields of this {@code ReusableLogEvent} from another event.
*
- * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot.
+ * This method is used on async logger ringbuffer slots holding ReusableLogEvent objects in each slot.
*
*
* @param event the event to copy data from
@@ -119,9 +119,7 @@ public void initFrom(final LogEvent event) {
setMessage(event.getMessage());
}
- /**
- * Clears all references this event has to other objects.
- */
+ @Override
public void clear() {
loggerFqcn = null;
marker = null;
@@ -166,6 +164,7 @@ public String getLoggerFqcn() {
return loggerFqcn;
}
+ @Override
public void setLoggerFqcn(final String loggerFqcn) {
this.loggerFqcn = loggerFqcn;
}
@@ -175,6 +174,7 @@ public Marker getMarker() {
return marker;
}
+ @Override
public void setMarker(final Marker marker) {
this.marker = marker;
}
@@ -187,6 +187,7 @@ public Level getLevel() {
return level;
}
+ @Override
public void setLevel(final Level level) {
this.level = level;
}
@@ -196,6 +197,7 @@ public String getLoggerName() {
return loggerName;
}
+ @Override
public void setLoggerName(final String loggerName) {
this.loggerName = loggerName;
}
@@ -208,6 +210,7 @@ public Message getMessage() {
return message;
}
+ @Override
public void setMessage(final Message msg) {
if (msg instanceof ReusableMessage) {
final ReusableMessage reusable = (ReusableMessage) msg;
@@ -312,6 +315,7 @@ public Throwable getThrown() {
return thrown;
}
+ @Override
public void setThrown(final Throwable thrown) {
this.thrown = thrown;
}
@@ -330,6 +334,7 @@ public long getTimeMillis() {
return instant.getEpochMillisecond();
}
+ @Override
public void setTimeMillis(final long timeMillis) {
this.instant.initFromEpochMilli(timeMillis, 0);
}
@@ -339,6 +344,11 @@ public Instant getInstant() {
return instant;
}
+ @Override
+ public void setInstant(final Instant instant) {
+ this.instant.initFrom(instant);
+ }
+
/**
* Returns the ThrowableProxy associated with the event, or null.
* @return The ThrowableProxy associated with the event.
@@ -351,6 +361,7 @@ public ThrowableProxy getThrownProxy() {
return thrownProxy;
}
+ @Override
public void setSource(final StackTraceElement source) {
this.source = source;
}
@@ -373,10 +384,11 @@ public StackTraceElement getSource() {
}
@Override
- public ReadOnlyStringMap getContextData() {
+ public StringMap getContextData() {
return contextData;
}
+ @Override
public void setContextData(final StringMap mutableContextData) {
this.contextData = mutableContextData;
}
@@ -386,6 +398,7 @@ public ThreadContext.ContextStack getContextStack() {
return contextStack;
}
+ @Override
public void setContextStack(final ThreadContext.ContextStack contextStack) {
this.contextStack = contextStack;
}
@@ -395,6 +408,7 @@ public long getThreadId() {
return threadId;
}
+ @Override
public void setThreadId(final long threadId) {
this.threadId = threadId;
}
@@ -404,6 +418,7 @@ public String getThreadName() {
return threadName;
}
+ @Override
public void setThreadName(final String threadName) {
this.threadName = threadName;
}
@@ -413,6 +428,7 @@ public int getThreadPriority() {
return threadPriority;
}
+ @Override
public void setThreadPriority(final int threadPriority) {
this.threadPriority = threadPriority;
}
@@ -442,14 +458,12 @@ public long getNanoTime() {
return nanoTime;
}
+ @Override
public void setNanoTime(final long nanoTime) {
this.nanoTime = nanoTime;
}
- /**
- * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}.
- * @param builder the builder whose fields to populate
- */
+ @Override
public void initializeBuilder(final Log4jLogEvent.Builder builder) {
builder.setContextData(contextData) //
.setContextStack(contextStack) //
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 3020455c06d..c4990416f50 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
@@ -16,39 +16,50 @@
*/
package org.apache.logging.log4j.core.impl;
+import java.util.List;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.ReusableLogEvent;
import org.apache.logging.log4j.core.async.ThreadNameCachingStrategy;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.plugins.Inject;
-import org.apache.logging.log4j.util.StringMap;
-
-import java.util.List;
+import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.RecyclerFactory;
/**
- * Garbage-free LogEventFactory that reuses a single mutable log event.
+ * Garbage-free LogEventFactory that recycles mutable LogEvent instances.
* @since 2.6
+ * @see Recycler
*/
public class ReusableLogEventFactory implements LogEventFactory {
private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create();
- private static final ThreadLocal mutableLogEventThreadLocal = new ThreadLocal<>();
-
private final ContextDataInjector injector;
private final Clock clock;
private final NanoClock nanoClock;
+ private final Recycler recycler;
@Inject
- public ReusableLogEventFactory(final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) {
+ public ReusableLogEventFactory(final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock,
+ final RecyclerFactory recyclerFactory) {
this.injector = injector;
this.clock = clock;
this.nanoClock = nanoClock;
+ this.recycler = recyclerFactory.create(() -> {
+ final MutableLogEvent event = new MutableLogEvent();
+ final Thread currentThread = Thread.currentThread();
+ event.setThreadId(currentThread.getId());
+ event.setThreadName(currentThread.getName());
+ event.setThreadPriority(currentThread.getPriority());
+ return event;
+ });
}
/**
@@ -86,8 +97,7 @@ public LogEvent createEvent(final String loggerName, final Marker marker,
public LogEvent createEvent(final String loggerName, final Marker marker,
final String fqcn, final StackTraceElement location, final Level level, final Message message,
final List properties, final Throwable t) {
- MutableLogEvent result = getOrCreateMutableLogEvent();
- result.reserved = true;
+ MutableLogEvent result = recycler.acquire();
// No need to clear here, values are cleared in release when reserved is set to false.
// If the event was dirty we'd create a new one.
@@ -99,7 +109,7 @@ public LogEvent createEvent(final String loggerName, final Marker marker,
result.initTime(clock, nanoClock);
result.setThrown(t);
result.setSource(location);
- result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData()));
+ result.setContextData(injector.injectContextData(properties, result.getContextData()));
result.setContextStack(ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack());// mutable copy
if (THREAD_NAME_CACHING_STRATEGY == ThreadNameCachingStrategy.UNCACHED) {
@@ -109,22 +119,14 @@ public LogEvent createEvent(final String loggerName, final Marker marker,
return result;
}
- private static MutableLogEvent getOrCreateMutableLogEvent() {
- MutableLogEvent result = mutableLogEventThreadLocal.get();
- return result == null || result.reserved ? createInstance(result) : result;
- }
-
- private static MutableLogEvent createInstance(MutableLogEvent existing) {
- MutableLogEvent result = new MutableLogEvent();
-
- // usually no need to re-initialize thread-specific fields since the event is stored in a ThreadLocal
- result.setThreadId(Thread.currentThread().getId());
- result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call
- result.setThreadPriority(Thread.currentThread().getPriority());
- if (existing == null) {
- mutableLogEventThreadLocal.set(result);
+ @Override
+ public void recycle(final LogEvent event) {
+ if (event instanceof ReusableLogEvent) {
+ ((ReusableLogEvent) event).clear();
+ if (event instanceof MutableLogEvent) {
+ recycler.release((MutableLogEvent) event);
+ }
}
- return result;
}
/**
@@ -132,12 +134,12 @@ private static MutableLogEvent createInstance(MutableLogEvent existing) {
* 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 MutableLogEvent) {
- final MutableLogEvent mutableLogEvent = (MutableLogEvent) logEvent;
- mutableLogEvent.clear();
- mutableLogEvent.reserved = false;
+ if (logEvent instanceof ReusableLogEvent) {
+ ((ReusableLogEvent) logEvent).clear();
}
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
index cc221a47bd1..a0fdc153a16 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
@@ -167,25 +167,30 @@ protected void markEvent() {
* Subclasses can override this method to provide a garbage-free implementation. For text-based layouts,
* {@code AbstractStringLayout} provides various convenience methods to help with this:
*
");
+ appendLs(sbuf, " ");
+ appendLs(sbuf, "");
+ return getBytes(sbuf.toString());
+ } finally {
+ releaseStringBuilder(sbuf);
+ }
}
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index 8af7ec90379..2f365b47a4b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -38,6 +38,9 @@
import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.spi.LoggingSystem;
+import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.RecyclerFactory;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.PropertyEnvironment;
import org.apache.logging.log4j.util.Strings;
@@ -194,22 +197,15 @@ public void serialize(final LogEvent event, final StringBuilder stringBuilder) {
@Override
public void encode(final LogEvent event, final ByteBufferDestination destination) {
- final StringBuilder text = toText(eventSerializer, event, getStringBuilder());
- final Encoder encoder = getStringBuilderEncoder();
- encoder.encode(text, destination);
- trimToMaxSize(text);
- }
-
- /**
- * Creates a text representation of the specified log event
- * and writes it into the specified StringBuilder.
- *
- * Implementations are free to return a new StringBuilder if they can
- * detect in advance that the specified StringBuilder is too small.
- */
- private StringBuilder toText(final Serializer2 serializer, final LogEvent event,
- final StringBuilder destination) {
- return serializer.toSerializable(event, destination);
+ final StringBuilder builder = acquireStringBuilder();
+ StringBuilder text = builder;
+ try {
+ text = eventSerializer.toSerializable(event, builder);
+ final Encoder encoder = getStringBuilderEncoder();
+ encoder.encode(text, destination);
+ } finally {
+ releaseStringBuilder(text);
+ }
}
/**
@@ -240,21 +236,23 @@ private interface PatternSerializer extends Serializer, Serializer2 {}
private static final class NoFormatPatternSerializer implements PatternSerializer {
private final LogEventPatternConverter[] converters;
+ private final Recycler recycler;
- private NoFormatPatternSerializer(final PatternFormatter[] formatters) {
+ private NoFormatPatternSerializer(final PatternFormatter[] formatters, final Recycler recycler) {
this.converters = new LogEventPatternConverter[formatters.length];
for (int i = 0; i < formatters.length; i++) {
converters[i] = formatters[i].getConverter();
}
+ this.recycler = recycler;
}
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder sb = getStringBuilder();
+ final StringBuilder sb = recycler.acquire();
try {
return toSerializable(event, sb).toString();
} finally {
- trimToMaxSize(sb);
+ recycler.release(sb);
}
}
@@ -285,18 +283,20 @@ public String toString() {
private static final class PatternFormatterPatternSerializer implements PatternSerializer {
private final PatternFormatter[] formatters;
+ private final Recycler recycler;
- private PatternFormatterPatternSerializer(final PatternFormatter[] formatters) {
+ private PatternFormatterPatternSerializer(final PatternFormatter[] formatters, final Recycler recycler) {
this.formatters = formatters;
+ this.recycler = recycler;
}
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder sb = getStringBuilder();
+ final StringBuilder sb = recycler.acquire();
try {
return toSerializable(event, sb).toString();
} finally {
- trimToMaxSize(sb);
+ recycler.release(sb);
}
}
@@ -321,19 +321,22 @@ private static final class PatternSerializerWithReplacement implements Serialize
private final PatternSerializer delegate;
private final RegexReplacement replace;
+ private final Recycler recycler;
- private PatternSerializerWithReplacement(final PatternSerializer delegate, final RegexReplacement replace) {
+ private PatternSerializerWithReplacement(final PatternSerializer delegate, final RegexReplacement replace,
+ final Recycler recycler) {
this.delegate = delegate;
this.replace = replace;
+ this.recycler = recycler;
}
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder sb = getStringBuilder();
+ final StringBuilder sb = recycler.acquire();
try {
return toSerializable(event, sb).toString();
} finally {
- trimToMaxSize(sb);
+ recycler.release(sb);
}
}
@@ -381,6 +384,10 @@ public Serializer build() {
if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) {
return null;
}
+ final RecyclerFactory recyclerFactory = configuration != null
+ ? configuration.getRecyclerFactory()
+ : LoggingSystem.getRecyclerFactory();
+ final Recycler recycler = createRecycler(recyclerFactory);
if (patternSelector == null) {
try {
final PatternParser parser = createPatternParser(configuration);
@@ -396,14 +403,16 @@ public Serializer build() {
}
}
PatternSerializer serializer = hasFormattingInfo
- ? new PatternFormatterPatternSerializer(formatters)
- : new NoFormatPatternSerializer(formatters);
- return replace == null ? serializer : new PatternSerializerWithReplacement(serializer, replace);
+ ? new PatternFormatterPatternSerializer(formatters, recycler)
+ : new NoFormatPatternSerializer(formatters, recycler);
+ return replace == null
+ ? serializer
+ : new PatternSerializerWithReplacement(serializer, replace, recycler);
} catch (final RuntimeException ex) {
throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex);
}
}
- return new PatternSelectorSerializer(patternSelector, replace);
+ return new PatternSelectorSerializer(patternSelector, replace, recycler);
}
public SerializerBuilder setConfiguration(final Configuration configuration) {
@@ -452,20 +461,23 @@ private static final class PatternSelectorSerializer implements Serializer, Seri
private final PatternSelector patternSelector;
private final RegexReplacement replace;
+ private final Recycler recycler;
- private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace) {
+ private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace,
+ final Recycler recycler) {
super();
this.patternSelector = patternSelector;
this.replace = replace;
+ this.recycler = recycler;
}
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder sb = getStringBuilder();
+ final StringBuilder sb = recycler.acquire();
try {
return toSerializable(event, sb).toString();
} finally {
- trimToMaxSize(sb);
+ recycler.release(sb);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
index f18b58025cf..23bfacf544b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
@@ -274,24 +274,28 @@ public Map getContentFormat() {
*/
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder buf = getStringBuilder();
- appendPriority(buf, event.getLevel());
- appendTimestamp(buf, event.getTimeMillis());
- appendSpace(buf);
- appendHostName(buf);
- appendSpace(buf);
- appendAppName(buf);
- appendSpace(buf);
- appendProcessId(buf);
- appendSpace(buf);
- appendMessageId(buf, event.getMessage());
- appendSpace(buf);
- appendStructuredElements(buf, event);
- appendMessage(buf, event);
- if (useTlsMessageFormat) {
- return new TlsSyslogFrame(buf.toString()).toString();
- }
- return buf.toString();
+ final StringBuilder buf = acquireStringBuilder();
+ try {
+ appendPriority(buf, event.getLevel());
+ appendTimestamp(buf, event.getTimeMillis());
+ appendSpace(buf);
+ appendHostName(buf);
+ appendSpace(buf);
+ appendAppName(buf);
+ appendSpace(buf);
+ appendProcessId(buf);
+ appendSpace(buf);
+ appendMessageId(buf, event.getMessage());
+ appendSpace(buf);
+ appendStructuredElements(buf, event);
+ appendMessage(buf, event);
+ if (useTlsMessageFormat) {
+ return new TlsSyslogFrame(buf.toString()).toString();
+ }
+ return buf.toString();
+ } finally {
+ releaseStringBuilder(buf);
+ }
}
private void appendPriority(final StringBuilder buffer, final Level logLevel) {
@@ -590,25 +594,25 @@ public String toString() {
/**
* Create the RFC 5424 Layout.
*
- * @param facility The Facility is used to try to classify the message.
- * @param id The default structured data id to use when formatting according to RFC 5424.
- * @param enterpriseNumber The IANA enterprise number.
- * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
- * record. Defaults to "true:.
- * @param mdcId The id to use for the MDC Structured Data Element.
- * @param mdcPrefix The prefix to add to MDC key names.
- * @param eventPrefix The prefix to add to event key names.
- * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
- * @param escapeNL String that should be used to replace newlines within the message text.
- * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
- * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
- * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent.
- * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent.
- * @param required A comma separated list of MDC keys that must be present in the MDC.
- * @param exceptionPattern The pattern for formatting exceptions.
+ * @param facility The Facility is used to try to classify the message.
+ * @param id The default structured data id to use when formatting according to RFC 5424.
+ * @param enterpriseNumber The IANA enterprise number.
+ * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
+ * record. Defaults to "true:.
+ * @param mdcId The id to use for the MDC Structured Data Element.
+ * @param mdcPrefix The prefix to add to MDC key names.
+ * @param eventPrefix The prefix to add to event key names.
+ * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
+ * @param escapeNL String that should be used to replace newlines within the message text.
+ * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
+ * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
+ * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent.
+ * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent.
+ * @param required A comma separated list of MDC keys that must be present in the MDC.
+ * @param exceptionPattern The pattern for formatting exceptions.
* @param useTlsMessageFormat If true the message will be formatted according to RFC 5425.
- * @param loggerFields Container for the KeyValuePairs containing the patterns
- * @param config The Configuration. Some Converters require access to the Interpolator.
+ * @param loggerFields Container for the KeyValuePairs containing the patterns
+ * @param config The Configuration. Some Converters require access to the Interpolator.
* @return An Rfc5424Layout.
* @deprecated Use {@link Rfc5424LayoutBuilder instead}
*/
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
index fb481233b62..be88b18009d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
@@ -145,26 +145,30 @@ protected SyslogLayout(final Facility facility, final boolean includeNL, final S
*/
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder buf = getStringBuilder();
-
- buf.append('<');
- buf.append(Priority.getPriority(facility, event.getLevel()));
- buf.append('>');
- addDate(event.getTimeMillis(), buf);
- buf.append(Chars.SPACE);
- buf.append(localHostname);
- buf.append(Chars.SPACE);
-
- String message = event.getMessage().getFormattedMessage();
- if (null != escapeNewLine) {
- message = NEWLINE_PATTERN.matcher(message).replaceAll(escapeNewLine);
+ final StringBuilder buf = acquireStringBuilder();
+
+ try {
+ buf.append('<');
+ buf.append(Priority.getPriority(facility, event.getLevel()));
+ buf.append('>');
+ addDate(event.getTimeMillis(), buf);
+ buf.append(Chars.SPACE);
+ buf.append(localHostname);
+ buf.append(Chars.SPACE);
+
+ String message = event.getMessage().getFormattedMessage();
+ if (null != escapeNewLine) {
+ message = NEWLINE_PATTERN.matcher(message).replaceAll(escapeNewLine);
+ }
+ buf.append(message);
+
+ if (includeNewLine) {
+ buf.append('\n');
+ }
+ return buf.toString();
+ } finally {
+ releaseStringBuilder(buf);
}
- buf.append(message);
-
- if (includeNewLine) {
- buf.append('\n');
- }
- return buf.toString();
}
private synchronized void addDate(final long timestamp, final StringBuilder buf) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java
index 62fc657e368..578da7717d4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java
@@ -112,7 +112,7 @@ private static boolean isJndiEnabled(final String subKey) {
* Maximum size of the StringBuilders used in RingBuffer LogEvents to store the contents of reusable Messages.
* After a large message has been delivered to the appenders, the StringBuilder is trimmed to this size.
*
- * The default value is {@value}, which allows the StringBuilder to resize three times from its initial size.
+ * The default value is 518, which allows the StringBuilder to resize three times from its initial size.
* Users can override with system property "log4j.maxReusableMsgSize".
*
* @since 2.6
diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
index fa38c66d1df..216af9a7f13 100644
--- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
+++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
@@ -16,6 +16,9 @@
*/
package org.apache.logging.log4j.csv.layout;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;
import org.apache.logging.log4j.core.Layout;
@@ -28,9 +31,6 @@
import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
-import java.io.IOException;
-import java.nio.charset.Charset;
-
/**
* A Comma-Separated Value (CSV) layout to log events.
*
@@ -77,7 +77,7 @@ protected CsvLogEventLayout(final Configuration config, final Charset charset, f
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder buffer = getStringBuilder();
+ final StringBuilder buffer = acquireStringBuilder();
final CSVFormat format = getFormat();
try {
format.print(event.getNanoTime(), buffer, true);
@@ -99,6 +99,8 @@ public String toSerializable(final LogEvent event) {
} catch (final IOException e) {
StatusLogger.getLogger().error(event.toString(), e);
return format.getCommentMarker() + " " + e;
+ } finally {
+ releaseStringBuilder(buffer);
}
}
diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
index 3a0eac0b817..cd83093fa0f 100644
--- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
+++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
@@ -16,6 +16,9 @@
*/
package org.apache.logging.log4j.csv.layout;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;
import org.apache.logging.log4j.core.Layout;
@@ -29,9 +32,6 @@
import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
-import java.io.IOException;
-import java.nio.charset.Charset;
-
/**
* A Comma-Separated Value (CSV) layout to log event parameters.
* The event message is currently ignored.
@@ -88,13 +88,15 @@ public CsvParameterLayout(final Configuration config, final Charset charset, fin
public String toSerializable(final LogEvent event) {
final Message message = event.getMessage();
final Object[] parameters = message.getParameters();
- final StringBuilder buffer = getStringBuilder();
+ final StringBuilder buffer = acquireStringBuilder();
try {
getFormat().printRecord(buffer, parameters);
return buffer.toString();
} catch (final IOException e) {
StatusLogger.getLogger().error(message, e);
return getFormat().getCommentMarker() + " " + e;
+ } finally {
+ releaseStringBuilder(buffer);
}
}
diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
index 7eacd23781a..0285a828039 100644
--- a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
+++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
@@ -521,7 +521,8 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception {
.setCharset(StandardCharsets.UTF_8)
.setIncludeStacktrace(true)
.build();
- Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj());
+ final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE;
+ Message message = factory.newMessage("Testing {}", new TestObj());
try {
final Log4jLogEvent expected = Log4jLogEvent.newBuilder()
.setLoggerName("a.B")
@@ -538,7 +539,7 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception {
final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class);
assertEquals(expectedMessage, actual.getMessage().getFormattedMessage());
} finally {
- ReusableMessageFactory.release(message);
+ factory.recycle(message);
}
}
@@ -556,7 +557,8 @@ public void testLayoutRingBufferEventReusableMessageWithCurlyBraces() throws Exc
.setCharset(StandardCharsets.UTF_8)
.setIncludeStacktrace(true)
.build();
- Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj());
+ final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE;
+ Message message = factory.newMessage("Testing {}", new TestObj());
try {
RingBufferLogEvent ringBufferEvent = new RingBufferLogEvent();
ringBufferEvent.setValues(
@@ -569,7 +571,7 @@ null, new SortedArrayStringMap(), ThreadContext.EMPTY_STACK, 1L,
final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class);
assertEquals(expectedMessage, actual.getMessage().getFormattedMessage());
} finally {
- ReusableMessageFactory.release(message);
+ factory.recycle(message);
}
}
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
index 201920ba4e8..7363a0b9a1f 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
@@ -44,8 +44,8 @@
import org.apache.logging.log4j.core.layout.GelfLayout;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
-import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecyclerFactory;
import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java
index ec97185074d..639fc0887f4 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java
@@ -16,22 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.test.appender.ListAppender;
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
import org.apache.logging.log4j.core.test.junit.Named;
-import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecycler;
+import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
/**
- * Tests if logging while trying to encode an event causes {@link ThreadLocalRecycler} to incorrectly share buffers and end up overriding layout's earlier encoding work.
+ * Tests if logging while trying to encode an event causes {@link ThreadLocalRecyclerFactory} to incorrectly share buffers and end up overriding layout's earlier encoding work.
*
* @see LOG4J2-2368
*/
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java
similarity index 89%
rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java
rename to log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java
index 1a9d23531c0..74771534aaa 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java
@@ -14,7 +14,11 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.util;
+package org.apache.logging.log4j.spi;
+
+import java.lang.reflect.Field;
+import java.util.ArrayDeque;
+import java.util.concurrent.ArrayBlockingQueue;
import org.apache.logging.log4j.core.test.appender.ListAppender;
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
@@ -27,10 +31,6 @@
import org.jctools.queues.MpmcArrayQueue;
import org.junit.jupiter.api.Test;
-import java.lang.reflect.Field;
-import java.util.ArrayDeque;
-import java.util.concurrent.ArrayBlockingQueue;
-
class RecyclerFactoriesTest {
@Test
@@ -79,9 +79,9 @@ void test_RecyclerFactoryConverter() throws Exception {
queueingRecyclerFactory.create(Object::new);
Assertions
.assertThat(recycler)
- .isInstanceOf(QueueingRecycler.class);
- final QueueingRecycler