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.resource provided + + 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: *

- *
@Category(Node.CATEGORY)
-     * @Plugin(value = "MyLayout", elementType = Layout.ELEMENT_TYPE, printObject = true)
+     * 
{@code @Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true)
+     * @Plugin("MyLayout")
      * public final class MyLayout extends AbstractStringLayout {
      *     @Override
      *     public void encode(LogEvent event, ByteBufferDestination destination) {
-     *         StringBuilder text = getStringBuilder();
-     *         convertLogEventToText(event, text);
-     *         getStringBuilderEncoder().encode(text, destination);
+     *         StringBuilder text = acquireStringBuilder();
+     *         try {
+     *             convertLogEventToText(event, text);
+     *             getStringBuilderEncoder().encode(text, destination);
+     *         } finally {
+     *             releaseStringBuilder(text);
+     *         }
      *     }
      *
      *     private void convertLogEventToText(LogEvent event, StringBuilder destination) {
      *         ... // append a text representation of the log event to the StringBuilder
      *     }
      * }
-     * 
+ * }
* * @param event the LogEvent to encode. * @param destination holds the ByteBuffer to write into. - * @see AbstractStringLayout#getStringBuilder() + * @see AbstractStringLayout#acquireStringBuilder() + * @see AbstractStringLayout#releaseStringBuilder(StringBuilder) * @see AbstractStringLayout#getStringBuilderEncoder() */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java index 1566173fede..da6e0629072 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java @@ -23,13 +23,15 @@ import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.StringEncoder; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; -import org.apache.logging.log4j.spi.AbstractLogger; +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.StringBuilders; import org.apache.logging.log4j.util.Strings; @@ -114,34 +116,16 @@ public interface Serializer2 { protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, size(Log4jProperties.GC_LAYOUT_STRING_BUILDER_MAX_SIZE, 2 * 1024)); - private static final ThreadLocal threadLocal = new ThreadLocal<>(); - - /** - * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. - * - * @return a {@code StringBuilder} - */ - protected static StringBuilder getStringBuilder() { - if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368 - // Recursive logging may clobber the cached StringBuilder. - return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); - } - StringBuilder result = threadLocal.get(); - if (result == null) { - result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); - threadLocal.set(result); - } - trimToMaxSize(result); - result.setLength(0); - return result; - } - private static int size(final String property, final int defaultValue) { return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); } - protected static void trimToMaxSize(final StringBuilder stringBuilder) { - StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); + protected static Recycler createRecycler(final RecyclerFactory recyclerFactory) { + return recyclerFactory.create( + () -> new StringBuilder(DEFAULT_STRING_BUILDER_SIZE), + stringBuilder -> stringBuilder.setLength(0), + stringBuilder -> StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE) + ); } private Encoder textEncoder; @@ -154,6 +138,10 @@ protected static void trimToMaxSize(final StringBuilder stringBuilder) { private final Serializer headerSerializer; + private final RecyclerFactory recyclerFactory; + + private final Recycler recycler; + protected AbstractStringLayout(final Charset charset) { this(charset, (byte[]) null, (byte[]) null); } @@ -171,6 +159,8 @@ protected AbstractStringLayout(final Charset aCharset, final byte[] header, fina this.footerSerializer = null; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; + recyclerFactory = LoggingSystem.getRecyclerFactory(); + recycler = createRecycler(recyclerFactory); } /** @@ -188,6 +178,8 @@ protected AbstractStringLayout(final Configuration config, final Charset aCharse this.footerSerializer = footerSerializer; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; + recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); + recycler = createRecycler(recyclerFactory); } protected byte[] getBytes(final String s) { @@ -235,11 +227,6 @@ public Serializer getHeaderSerializer() { return headerSerializer; } - private DefaultLogEventFactory getLogEventFactory() { - // TODO: inject this - return DefaultLogEventFactory.newInstance(); - } - /** * Returns a {@code Encoder} that this Layout implementation can use for encoding log events. * @@ -252,6 +239,20 @@ protected Encoder getStringBuilderEncoder() { return textEncoder; } + /** + * Returns a StringBuilder that may be recycled via {@link #releaseStringBuilder(StringBuilder)} when done being used. + */ + protected StringBuilder acquireStringBuilder() { + return recycler.acquire(); + } + + /** + * Recycles a StringBuilder acquired via {@link #acquireStringBuilder()} so that it may be acquired again later. + */ + protected void releaseStringBuilder(final StringBuilder stringBuilder) { + recycler.release(stringBuilder); + } + protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { final String serializable = serializeToString(serializer); if (serializable == null) { @@ -264,9 +265,10 @@ protected String serializeToString(final Serializer serializer) { if (serializer == null) { return null; } - final LoggerConfig rootLogger = getConfiguration().getRootLogger(); + final LoggerConfig rootLogger = configuration.getRootLogger(); + final LogEventFactory logEventFactory = configuration.getLogEventFactory(); // Using "" for the FQCN, does it matter? - final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, + final LogEvent logEvent = logEventFactory.createEvent(rootLogger.getName(), null, Strings.EMPTY, rootLogger.getLevel(), null, null, null); return serializer.toSerializable(logEvent); } @@ -284,4 +286,8 @@ public byte[] toByteArray(final LogEvent event) { @Override public abstract String toSerializable(LogEvent event); + + public RecyclerFactory getRecyclerFactory() { + return recyclerFactory; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java index 610a70ade05..bd0d0f6bcb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java @@ -16,6 +16,19 @@ */ package org.apache.logging.log4j.core.layout; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -29,32 +42,24 @@ import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.StringBuilderWriter; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.plugins.Plugin; 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.status.StatusLogger; import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.GZIPOutputStream; - /** * Lays out events in the Graylog Extended Log Format (GELF) 1.1. *

@@ -112,6 +117,7 @@ public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) th private final PatternLayout layout; private final FieldWriter mdcWriter; private final FieldWriter mapWriter; + private final Recycler stacktraceRecycler; public static class Builder> extends AbstractStringLayout.Builder implements org.apache.logging.log4j.plugins.util.Builder { @@ -170,6 +176,8 @@ public static class Builder> extends AbstractStringLayout.B @PluginElement("PatternSelector") private PatternSelector patternSelector = null; + private RecyclerFactory recyclerFactory; + public Builder() { super(); setCharset(StandardCharsets.UTF_8); @@ -185,22 +193,26 @@ public GelfLayout build() { + "ignoring message pattern"); messagePattern = null; } + final Configuration config = getConfiguration(); if (messagePattern != null) { patternLayout = PatternLayout.newBuilder().setPattern(messagePattern) .setAlwaysWriteExceptions(includeStacktrace) - .setConfiguration(getConfiguration()) + .setConfiguration(config) .build(); } if (patternSelector != null) { patternLayout = PatternLayout.newBuilder().setPatternSelector(patternSelector) .setAlwaysWriteExceptions(includeStacktrace) - .setConfiguration(getConfiguration()) + .setConfiguration(config) .build(); } - return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold, + if (recyclerFactory == null) { + recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); + } + return new GelfLayout(config, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter, includeNewLineDelimiter, omitEmptyFields, mdcChecker, mapChecker, patternLayout, - threadContextPrefix, mapPrefix); + threadContextPrefix, mapPrefix, recyclerFactory); } private ListChecker createChecker(final String excludes, final String includes) { @@ -436,6 +448,12 @@ public B setMapPrefix(final String prefix) { } return asBuilder(); } + + @Inject + public B setRecyclerFactory(final RecyclerFactory recyclerFactory) { + this.recyclerFactory = recyclerFactory; + return asBuilder(); + } } private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, @@ -443,7 +461,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue final boolean includeThreadContext, final boolean includeMapMessage, final boolean includeNullDelimiter, final boolean includeNewLineDelimiter, final boolean omitEmptyFields, final ListChecker mdcChecker, final ListChecker mapChecker, final PatternLayout patternLayout, final String mdcPrefix, - final String mapPrefix) { + final String mapPrefix, final RecyclerFactory recyclerFactory) { super(config, StandardCharsets.UTF_8, null, null); this.host = host != null ? host : NetUtils.getLocalHostname(); this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0]; @@ -468,6 +486,11 @@ private GelfLayout(final Configuration config, final String host, final KeyValue this.mdcWriter = new FieldWriter(mdcChecker, mdcPrefix); this.mapWriter = new FieldWriter(mapChecker, mapPrefix); this.layout = patternLayout; + stacktraceRecycler = recyclerFactory.create( + () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE), + writer -> writer.getBuilder().setLength(0), + writer -> StringBuilders.trimToMaxSize(writer.getBuilder(), MAX_STRING_BUILDER_SIZE) + ); } @Override @@ -489,7 +512,7 @@ public String toString() { sb.append(", ").append(mapVars); } if (layout != null) { - sb.append(", PatternLayout{").append(layout.toString()).append("}"); + sb.append(", PatternLayout{").append(layout).append("}"); } return sb.toString(); } @@ -511,8 +534,13 @@ public String getContentType() { @Override public byte[] toByteArray(final LogEvent event) { - final StringBuilder text = toText(event, getStringBuilder(), false); - final byte[] bytes = getBytes(text.toString()); + final StringBuilder text = acquireStringBuilder(); + final byte[] bytes; + try { + bytes = getBytes(toText(event, text, false).toString()); + } finally { + releaseStringBuilder(text); + } return compressionType != CompressionType.OFF && bytes.length > compressionThreshold ? compress(bytes) : bytes; } @@ -522,9 +550,13 @@ public void encode(final LogEvent event, final ByteBufferDestination destination super.encode(event, destination); return; } - final StringBuilder text = toText(event, getStringBuilder(), true); - final Encoder helper = getStringBuilderEncoder(); - helper.encode(text, destination); + final StringBuilder text = acquireStringBuilder(); + try { + final Encoder helper = getStringBuilderEncoder(); + helper.encode(toText(event, text, true), destination); + } finally { + releaseStringBuilder(text); + } } @Override @@ -551,8 +583,12 @@ private byte[] compress(final byte[] bytes) { @Override public String toSerializable(final LogEvent event) { - final StringBuilder text = toText(event, getStringBuilder(), false); - return text.toString(); + final StringBuilder text = acquireStringBuilder(); + try { + return toText(event, text, false).toString(); + } finally { + releaseStringBuilder(text); + } } private StringBuilder toText(final LogEvent event, final StringBuilder builder, final boolean gcFree) { @@ -561,7 +597,9 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, builder.append("\"host\":\""); JsonUtils.quoteAsString(toNullSafeString(host), builder); builder.append(QC); - builder.append("\"timestamp\":").append(formatTimestamp(event.getTimeMillis())).append(C); + builder.append("\"timestamp\":"); + formatTimestampTo(builder, event.getTimeMillis()); + builder.append(C); builder.append("\"level\":").append(formatLevel(event.getLevel())).append(C); if (event.getThreadName() != null) { builder.append("\"_thread\":\""); @@ -598,12 +636,22 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, if (event.getThrown() != null || layout != null) { builder.append("\"full_message\":\""); if (layout != null) { - final StringBuilder messageBuffer = getMessageStringBuilder(); - layout.serialize(event, messageBuffer); - JsonUtils.quoteAsString(messageBuffer, builder); + final StringBuilder messageBuffer = acquireStringBuilder(); + try { + layout.serialize(event, messageBuffer); + JsonUtils.quoteAsString(messageBuffer, builder); + } finally { + releaseStringBuilder(messageBuffer); + } } else { if (includeStacktrace) { - JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder); + final StringBuilderWriter writer = stacktraceRecycler.acquire(); + try { + formatThrowableTo(writer, event.getThrown()); + JsonUtils.quoteAsString(writer.getBuilder(), builder); + } finally { + stacktraceRecycler.release(writer); + } } else { JsonUtils.quoteAsString(event.getThrown().toString(), builder); } @@ -616,12 +664,12 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, if (message instanceof CharSequence) { JsonUtils.quoteAsString(((CharSequence) message), builder); } else if (gcFree && message instanceof StringBuilderFormattable) { - final StringBuilder messageBuffer = getMessageStringBuilder(); + final StringBuilder messageBuffer = acquireStringBuilder(); try { ((StringBuilderFormattable) message).formatTo(messageBuffer); JsonUtils.quoteAsString(messageBuffer, builder); } finally { - trimToMaxSize(messageBuffer); + releaseStringBuilder(messageBuffer); } } else { JsonUtils.quoteAsString(toNullSafeString(message.getFormattedMessage()), builder); @@ -667,18 +715,6 @@ public ListChecker getChecker() { } } - private static final ThreadLocal messageStringBuilder = new ThreadLocal<>(); - - private static StringBuilder getMessageStringBuilder() { - StringBuilder result = messageStringBuilder.get(); - if (result == null) { - result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); - messageStringBuilder.set(result); - } - result.setLength(0); - return result; - } - private static CharSequence toNullSafeString(final CharSequence s) { return s == null ? Strings.EMPTY : s; } @@ -690,22 +726,17 @@ static CharSequence formatTimestamp(final long timeMillis) { if (timeMillis < 1000) { return "0"; } - final StringBuilder builder = getTimestampStringBuilder(); - builder.append(timeMillis); - builder.insert(builder.length() - 3, '.'); + final StringBuilder builder = new StringBuilder(20); + formatTimestampTo(builder, timeMillis); return builder; } - private static final ThreadLocal timestampStringBuilder = new ThreadLocal<>(); - - private static StringBuilder getTimestampStringBuilder() { - StringBuilder result = timestampStringBuilder.get(); - if (result == null) { - result = new StringBuilder(20); - timestampStringBuilder.set(result); + private static void formatTimestampTo(final StringBuilder builder, final long timeMillis) { + if (timeMillis < 1000) { + builder.append(0); + } else { + builder.append(timeMillis).insert(builder.length() - 3, '.'); } - result.setLength(0); - return result; } /** @@ -720,10 +751,14 @@ private int formatLevel(final Level level) { */ static CharSequence formatThrowable(final Throwable throwable) { // stack traces are big enough to provide a reasonably large initial capacity here - final StringWriter sw = new StringWriter(2048); - final PrintWriter pw = new PrintWriter(sw); + final StringBuilderWriter writer = new StringBuilderWriter(2048); + formatThrowableTo(writer, throwable); + return writer.getBuilder(); + } + + private static void formatThrowableTo(final StringBuilderWriter writer, final Throwable throwable) { + final PrintWriter pw = new PrintWriter(writer); throwable.printStackTrace(pw); pw.flush(); - return sw.getBuffer(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java index 5d61ee7e2f2..74afc2db8f3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java @@ -16,18 +16,6 @@ */ package org.apache.logging.log4j.core.layout; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.pattern.DatePatternConverter; -import org.apache.logging.log4j.core.util.Transform; -import org.apache.logging.log4j.plugins.Configurable; -import org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.util.Strings; - import java.io.IOException; import java.io.InterruptedIOException; import java.io.LineNumberReader; @@ -40,6 +28,18 @@ import java.util.ArrayList; import java.util.Date; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.pattern.DatePatternConverter; +import org.apache.logging.log4j.core.util.Transform; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.Strings; + /** * Outputs events as rows in an HTML table on an HTML page. *

@@ -149,85 +149,89 @@ private String addCharsetToContentType(final String contentType) { */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder sbuf = getStringBuilder(); + final StringBuilder sbuf = acquireStringBuilder(); + try { - sbuf.append(Strings.LINE_SEPARATOR).append("").append(Strings.LINE_SEPARATOR); + sbuf.append(Strings.LINE_SEPARATOR).append("").append(Strings.LINE_SEPARATOR); - sbuf.append(""); + sbuf.append(""); - if (datePatternConverter == null) { - sbuf.append(event.getTimeMillis() - jvmStartTime); - } else { - datePatternConverter.format(event, sbuf); - } - sbuf.append("").append(Strings.LINE_SEPARATOR); - - final String escapedThread = Transform.escapeHtmlTags(event.getThreadName()); - sbuf.append(""); - sbuf.append(escapedThread); - sbuf.append("").append(Strings.LINE_SEPARATOR); - - sbuf.append(""); - if (event.getLevel().equals(Level.DEBUG)) { - sbuf.append(""); - sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); - sbuf.append(""); - } else if (event.getLevel().isMoreSpecificThan(Level.WARN)) { - sbuf.append(""); - sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); - sbuf.append(""); - } else { - sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); - } - sbuf.append("").append(Strings.LINE_SEPARATOR); + if (datePatternConverter == null) { + sbuf.append(event.getTimeMillis() - jvmStartTime); + } else { + datePatternConverter.format(event, sbuf); + } + sbuf.append("").append(Strings.LINE_SEPARATOR); - String escapedLogger = Transform.escapeHtmlTags(event.getLoggerName()); - if (Strings.isEmpty(escapedLogger)) { - escapedLogger = LoggerConfig.ROOT; - } - sbuf.append(""); - sbuf.append(escapedLogger); - sbuf.append("").append(Strings.LINE_SEPARATOR); + final String escapedThread = Transform.escapeHtmlTags(event.getThreadName()); + sbuf.append(""); + sbuf.append(escapedThread); + sbuf.append("").append(Strings.LINE_SEPARATOR); - if (locationInfo) { - final StackTraceElement element = event.getSource(); - sbuf.append(""); - sbuf.append(Transform.escapeHtmlTags(element.getFileName())); - sbuf.append(':'); - sbuf.append(element.getLineNumber()); + sbuf.append(""); + if (event.getLevel().equals(Level.DEBUG)) { + sbuf.append(""); + sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); + sbuf.append(""); + } else if (event.getLevel().isMoreSpecificThan(Level.WARN)) { + sbuf.append(""); + sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); + sbuf.append(""); + } else { + sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel()))); + } sbuf.append("").append(Strings.LINE_SEPARATOR); - } - sbuf.append(""); - sbuf.append(Transform.escapeHtmlTags(event.getMessage().getFormattedMessage()).replaceAll(REGEXP, "
")); - sbuf.append("").append(Strings.LINE_SEPARATOR); - sbuf.append("").append(Strings.LINE_SEPARATOR); - - if (event.getContextStack() != null && !event.getContextStack().isEmpty()) { - sbuf.append(""); - sbuf.append("NDC: ").append(Transform.escapeHtmlTags(event.getContextStack().toString())); - sbuf.append("").append(Strings.LINE_SEPARATOR); - } + String escapedLogger = Transform.escapeHtmlTags(event.getLoggerName()); + if (Strings.isEmpty(escapedLogger)) { + escapedLogger = LoggerConfig.ROOT; + } + sbuf.append(""); + sbuf.append(escapedLogger); + sbuf.append("").append(Strings.LINE_SEPARATOR); - if (event.getContextData() != null && !event.getContextData().isEmpty()) { - sbuf.append(""); - sbuf.append("MDC: ").append(Transform.escapeHtmlTags(event.getContextData().toMap().toString())); - sbuf.append("").append(Strings.LINE_SEPARATOR); - } + if (locationInfo) { + final StackTraceElement element = event.getSource(); + sbuf.append(""); + sbuf.append(Transform.escapeHtmlTags(element.getFileName())); + sbuf.append(':'); + sbuf.append(element.getLineNumber()); + sbuf.append("").append(Strings.LINE_SEPARATOR); + } - final Throwable throwable = event.getThrown(); - if (throwable != null) { - sbuf.append(""); - appendThrowableAsHtml(throwable, sbuf); - sbuf.append("").append(Strings.LINE_SEPARATOR); - } + sbuf.append(""); + sbuf.append(Transform.escapeHtmlTags(event.getMessage().getFormattedMessage()).replaceAll(REGEXP, "
")); + sbuf.append("").append(Strings.LINE_SEPARATOR); + sbuf.append("").append(Strings.LINE_SEPARATOR); + + if (event.getContextStack() != null && !event.getContextStack().isEmpty()) { + sbuf.append(""); + sbuf.append("NDC: ").append(Transform.escapeHtmlTags(event.getContextStack().toString())); + sbuf.append("").append(Strings.LINE_SEPARATOR); + } - return sbuf.toString(); + if (event.getContextData() != null && !event.getContextData().isEmpty()) { + sbuf.append(""); + sbuf.append("MDC: ").append(Transform.escapeHtmlTags(event.getContextData().toMap().toString())); + sbuf.append("").append(Strings.LINE_SEPARATOR); + } + + final Throwable throwable = event.getThrown(); + if (throwable != null) { + sbuf.append(""); + appendThrowableAsHtml(throwable, sbuf); + sbuf.append("").append(Strings.LINE_SEPARATOR); + } + + return sbuf.toString(); + } finally { + releaseStringBuilder(sbuf); + } } @Override @@ -289,41 +293,45 @@ private StringBuilder append(final StringBuilder sbuilder, final String s) { */ @Override public byte[] getHeader() { - final StringBuilder sbuf = new StringBuilder(); - append(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - append(sbuf, ""); - append(sbuf, "").append(title); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, "


"); - appendLs(sbuf, "Log session start time " + new Date() + "
"); - appendLs(sbuf, "
"); - appendLs(sbuf, - ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - if (locationInfo) { - appendLs(sbuf, ""); + final StringBuilder sbuf = acquireStringBuilder(); + try { + append(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + append(sbuf, ""); + append(sbuf, "").append(title); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, "
"); + appendLs(sbuf, "Log session start time " + new Date() + "
"); + appendLs(sbuf, "
"); + appendLs(sbuf, + "
TimeThreadLevelLoggerFile:Line
"); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + if (locationInfo) { + appendLs(sbuf, ""); + } + appendLs(sbuf, ""); + appendLs(sbuf, ""); + return sbuf.toString().getBytes(getCharset()); + } finally { + releaseStringBuilder(sbuf); } - appendLs(sbuf, ""); - appendLs(sbuf, ""); - return sbuf.toString().getBytes(getCharset()); } /** @@ -332,11 +340,15 @@ public byte[] getHeader() { */ @Override public byte[] getFooter() { - final StringBuilder sbuf = new StringBuilder(); - appendLs(sbuf, "
TimeThreadLevelLoggerFile:LineMessage
Message
"); - appendLs(sbuf, "
"); - appendLs(sbuf, ""); - return getBytes(sbuf.toString()); + final StringBuilder sbuf = acquireStringBuilder(); + try { + appendLs(sbuf, ""); + 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 queueingRecycler = - (QueueingRecycler) recycler; + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; Assertions .assertThat(queueingRecycler.getQueue()) .isInstanceOf(ArrayDeque.class); @@ -111,9 +111,9 @@ void test_RecyclerFactoryConverter() throws Exception { queueingRecyclerFactory.create(Object::new); Assertions .assertThat(recycler) - .isInstanceOf(QueueingRecycler.class); - final QueueingRecycler queueingRecycler = - (QueueingRecycler) recycler; + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; Assertions .assertThat(queueingRecycler.getQueue()) .isInstanceOf(ArrayBlockingQueue.class); @@ -132,7 +132,7 @@ void test_RecyclerFactoryConverter_using_XML_config( final JsonTemplateLayout layout = (JsonTemplateLayout) appender.getLayout(); final Field field = JsonTemplateLayout.class.getDeclaredField("contextRecycler"); field.setAccessible(true); - final QueueingRecycler contextRecycler = (QueueingRecycler) field.get(layout); + final QueueingRecyclerFactory.QueueingRecycler contextRecycler = (QueueingRecyclerFactory.QueueingRecycler) field.get(layout); final MpmcArrayQueue queue = (MpmcArrayQueue) contextRecycler.getQueue(); Assertions.assertThat(queue.capacity()).isEqualTo(512); } diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java similarity index 96% rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java rename to log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java index f2790708d92..9e17e08e126 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java @@ -14,15 +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.layout.template.json.util.StringParameterParser.DoubleQuotedStringValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.NullValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.StringValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Value; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; +package org.apache.logging.log4j.util; import java.util.Collections; import java.util.LinkedHashMap; @@ -30,6 +22,15 @@ import java.util.Map; import java.util.Set; +import org.apache.logging.log4j.util.StringParameterParser; +import org.apache.logging.log4j.util.StringParameterParser.DoubleQuotedStringValue; +import org.apache.logging.log4j.util.StringParameterParser.NullValue; +import org.apache.logging.log4j.util.StringParameterParser.StringValue; +import org.apache.logging.log4j.util.StringParameterParser.Value; +import org.apache.logging.log4j.util.StringParameterParser.Values; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + @SuppressWarnings("DoubleBraceInitialization") class StringParameterParserTest { diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java index 4e57726fc22..1bd4895de16 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java @@ -16,6 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; @@ -28,24 +38,14 @@ import org.apache.logging.log4j.core.util.StringEncoder; import org.apache.logging.log4j.layout.template.json.resolver.*; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; import org.apache.logging.log4j.layout.template.json.util.Uris; import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - @Configurable(elementType = Layout.ELEMENT_TYPE) @Plugin public class JsonTemplateLayout implements StringLayout { diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java index e47acfb94a5..db77071681c 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java @@ -21,8 +21,8 @@ import java.util.Locale; import java.util.TimeZone; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactories; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java index aa4a1392dcb..139a699f19f 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java @@ -16,16 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; - import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.spi.Recycler; + /** * Resolves a number from an internal counter. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java index d7a5793d28d..28038ae81f9 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java @@ -16,18 +16,18 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Objects; + import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.Strings; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Objects; - /** * {@link TemplateResolverContext} specialized for {@link LogEvent}s. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java index 9b8182ea89e..3dc80ad16a8 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java @@ -18,10 +18,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ParameterConsumer; import org.apache.logging.log4j.message.ParameterVisitable; +import org.apache.logging.log4j.spi.Recycler; /** * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java index 788fa5de4df..7ea4730186f 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java @@ -6,7 +6,7 @@ * (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 + * 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, @@ -16,18 +16,18 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.TriConsumer; - import java.util.Map; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.TriConsumer; + /** * {@link ReadOnlyStringMap} resolver. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java index 92c2d33252c..35c2f23ca12 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java @@ -16,14 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.layout.template.json.util.*; - import java.util.List; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.apache.logging.log4j.layout.template.json.util.*; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; + /** * Exception stack trace to JSON string resolver used by {@link ExceptionResolver}. */ diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java deleted file mode 100644 index b83ae2f3033..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.Queue; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class QueueingRecycler implements Recycler { - - private final Supplier supplier; - - private final Consumer cleaner; - - private final Queue queue; - - public QueueingRecycler( - final Supplier supplier, - final Consumer cleaner, - final Queue queue) { - this.supplier = supplier; - this.cleaner = cleaner; - 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 { - cleaner.accept(value); - return value; - } - } - - @Override - public void release(final V value) { - queue.offer(value); - } - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java deleted file mode 100644 index 85b04ab7e3c..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.Queue; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class QueueingRecyclerFactory implements RecyclerFactory { - - private final Supplier> queueSupplier; - - public QueueingRecyclerFactory(final Supplier> queueSupplier) { - this.queueSupplier = queueSupplier; - } - - @Override - public Recycler create( - final Supplier supplier, - final Consumer cleaner) { - @SuppressWarnings("unchecked") - final Queue queue = (Queue) queueSupplier.get(); - return new QueueingRecycler<>(supplier, cleaner, queue); - } - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java deleted file mode 100644 index 2b4d236758f..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.function.Supplier; - -import org.apache.logging.log4j.util.LoaderUtil; -import org.jctools.queues.MpmcArrayQueue; - -import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; - -public final class RecyclerFactories { - - private RecyclerFactories() {} - - private static final String JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH = - "org.jctools.queues.MpmcArrayQueue.new"; - - private static final boolean JCTOOLS_QUEUE_CLASS_AVAILABLE = - isJctoolsQueueClassAvailable(); - - private static boolean isJctoolsQueueClassAvailable() { - try { - final String className = JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH - .replaceAll("\\.new$", ""); - LoaderUtil.loadClass(className); - return true; - } catch (final ClassNotFoundException ignored) { - return false; - } - } - - public static RecyclerFactory ofSpec(final String recyclerFactorySpec) { - - // Determine the default capacity. - final int defaultCapacity = Math.max( - 2 * Runtime.getRuntime().availableProcessors() + 1, - 8); - - // TLA-, MPMC-, or ABQ-based queueing factory -- if nothing is specified. - if (recyclerFactorySpec == null) { - if (isThreadLocalsEnabled()) { - return ThreadLocalRecyclerFactory.getInstance(); - } else { - final Supplier> queueSupplier = - JCTOOLS_QUEUE_CLASS_AVAILABLE - ? () -> new MpmcArrayQueue<>(defaultCapacity) - : () -> new ArrayBlockingQueue<>(defaultCapacity); - return new QueueingRecyclerFactory(queueSupplier); - } - } - - // 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")) { - 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, - new LinkedHashSet<>(Arrays.asList("supplier", "capacity"))); - - // Read the supplier path. - final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); - final String supplierPath; - if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) { - supplierPath = JCTOOLS_QUEUE_CLASS_AVAILABLE - ? JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH - : "java.util.concurrent.ArrayBlockingQueue.new"; - } else { - supplierPath = supplierValue.toString(); - } - - // 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); - } - } - - // Execute the read spec. - return createRecyclerFactory(queueFactorySpec, supplierPath, capacity); - - } - - private static RecyclerFactory createRecyclerFactory( - final String queueFactorySpec, - final String supplierPath, - final int capacity) { - final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.'); - if (supplierPathSplitterIndex < 0) { - throw new IllegalArgumentException( - "invalid supplier in queueing recycler factory: " + - queueFactorySpec); - } - final String supplierClassName = supplierPath.substring(0, supplierPathSplitterIndex); - final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1); - try { - final Class supplierClass = LoaderUtil.loadClass(supplierClassName); - final Supplier> queueSupplier; - if ("new".equals(supplierMethodName)) { - final Constructor supplierCtor = - supplierClass.getDeclaredConstructor(int.class); - queueSupplier = () -> { - try { - @SuppressWarnings("unchecked") - final Queue typedQueue = - (Queue) supplierCtor.newInstance(capacity); - return typedQueue; - } catch (final Exception error) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, error); - } - }; - } else { - final Method supplierMethod = - supplierClass.getMethod(supplierMethodName, int.class); - queueSupplier = () -> { - try { - @SuppressWarnings("unchecked") - final Queue typedQueue = - (Queue) supplierMethod.invoke(null, capacity); - return typedQueue; - } catch (final Exception error) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, error); - } - }; - } - return new QueueingRecyclerFactory(queueSupplier); - } catch (final Exception error) { - throw new RuntimeException( - "failed executing queueing recycler factory: " + - queueFactorySpec, error); - } - } - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java deleted file mode 100644 index 16b945c09b7..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -@FunctionalInterface -public interface RecyclerFactory { - - default Recycler create(final Supplier supplier) { - return create(supplier, ignored -> {}); - } - - Recycler create(Supplier supplier, Consumer cleaner); - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java index ce0dc999bfc..4d596872085 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java @@ -19,6 +19,8 @@ import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.convert.TypeConverter; import org.apache.logging.log4j.plugins.convert.TypeConverters; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; /** * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java deleted file mode 100644 index 99787553373..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class ThreadLocalRecycler implements Recycler { - - private final Consumer cleaner; - - private final ThreadLocal holder; - - public ThreadLocalRecycler( - final Supplier supplier, - final Consumer cleaner) { - this.cleaner = cleaner; - this.holder = ThreadLocal.withInitial(supplier); - } - - @Override - public V acquire() { - final V value = holder.get(); - cleaner.accept(value); - return value; - } - - @Override - public void release(final V value) {} - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java deleted file mode 100644 index 048262cf4e0..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class ThreadLocalRecyclerFactory implements RecyclerFactory { - - private static final ThreadLocalRecyclerFactory INSTANCE = - new ThreadLocalRecyclerFactory(); - - private ThreadLocalRecyclerFactory() {} - - public static ThreadLocalRecyclerFactory getInstance() { - return INSTANCE; - } - - @Override - public Recycler create( - final Supplier supplier, - final Consumer cleaner) { - return new ThreadLocalRecycler<>(supplier, cleaner); - } - -} diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java index 5233d8bb5f8..4a8c21517fc 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.jackson.json.layout.JsonLayout; 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.spi.ThreadLocalRecyclerFactory; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java index 959f6b2bb69..1020250bcd9 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.perf.jmh; import java.nio.ByteBuffer; @@ -215,9 +214,13 @@ public String toSerializable(final LogEvent event) { @Override public byte[] toByteArray(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); - ((StringBuilderFormattable) event.getMessage()).formatTo(sb); - return getBytes(sb.toString()); + final StringBuilder sb = acquireStringBuilder(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + return getBytes(sb.toString()); + } finally { + releaseStringBuilder(sb); + } } } @@ -238,10 +241,14 @@ public byte[] toByteArray(final LogEvent event) { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder sb = getStringBuilder(); - ((StringBuilderFormattable) event.getMessage()).formatTo(sb); - final Encoder helper = getStringBuilderEncoder(); - helper.encode(sb, destination); + final StringBuilder sb = acquireStringBuilder(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + final Encoder helper = getStringBuilderEncoder(); + helper.encode(sb, destination); + } finally { + releaseStringBuilder(sb); + } } }