diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java index b16ea9dc2af..f212b4ae868 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java @@ -142,6 +142,7 @@ private Appender createAppender(final String name, final Log4j1Configuration con resolveSyslogHost(syslogHost, host, port); final org.apache.logging.log4j.core.Layout messageLayout = LayoutAdapter.adapt(layout); final Log4j1SyslogLayout appenderLayout = Log4j1SyslogLayout.newBuilder() + .setConfiguration(configuration) .setHeader(header) .setFacility(Facility.toFacility(facility)) .setFacilityPrinting(facilityPrinting) diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java index 1bf7ac9c08f..91df856665d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java @@ -25,6 +25,7 @@ import org.apache.log4j.builders.AbstractBuilder; import org.apache.log4j.config.PropertiesConfiguration; import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.HtmlLayout; import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; @@ -78,6 +79,7 @@ public Layout parse(final PropertiesConfiguration config) { private Layout createLayout(final String title, final boolean locationInfo) { return LayoutWrapper.adapt(HtmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setTitle(title) .setLocationInfo(locationInfo) .build()); diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java index 82b6eb52b5f..01c0418dd56 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java @@ -25,6 +25,7 @@ import org.apache.log4j.config.PropertiesConfiguration; import org.apache.log4j.layout.Log4j1XmlLayout; import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; import org.w3c.dom.Element; @@ -73,6 +74,6 @@ public Layout parse(final PropertiesConfiguration config) { } private Layout createLayout(final boolean properties, final boolean locationInfo) { - return LayoutWrapper.adapt(Log4j1XmlLayout.createLayout(locationInfo, properties)); + return LayoutWrapper.adapt(Log4j1XmlLayout.createLayout(new DefaultConfiguration(), locationInfo, properties)); } } 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 c167442aee5..0cfb7aa3203 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 @@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.layout.AbstractStringLayout; import org.apache.logging.log4j.core.net.Facility; @@ -85,7 +86,7 @@ public Log4j1SyslogLayout build() { LOGGER.error("Log4j1SyslogLayout: the message layout must be a StringLayout."); return null; } - return new Log4j1SyslogLayout(facility, facilityPrinting, header, (StringLayout) messageLayout, getCharset()); + return new Log4j1SyslogLayout(getConfiguration(), facility, facilityPrinting, header, (StringLayout) messageLayout, getCharset()); } public Facility getFacility() { @@ -147,9 +148,14 @@ public static > B newBuilder() { private final LogEventPatternConverter dateConverter = DatePatternConverter.newInstance(dateFormatOptions); - private Log4j1SyslogLayout(final Facility facility, final boolean facilityPrinting, final boolean header, - final StringLayout messageLayout, final Charset charset) { - super(charset); + private Log4j1SyslogLayout( + final Configuration config, + final Facility facility, + final boolean facilityPrinting, + final boolean header, + final StringLayout messageLayout, + final Charset charset) { + super(config, charset); this.facility = facility; this.facilityPrinting = facilityPrinting; this.header = header; @@ -168,32 +174,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 = stringBuilderRecycler.acquire(); + + 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 ? toRootLowerCase(facility.name()) : "user").append(':'); } - buf.append(message); - // TODO: splitting message into 1024 byte chunks? - return buf.toString(); + buf.append(message); + // TODO: splitting message into 1024 byte chunks? + return buf.toString(); + } finally { + stringBuilderRecycler.release(buf); + } } /** 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 66143715ae4..1ede6c4af5d 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 @@ -25,8 +25,11 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.layout.AbstractStringLayout; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.Encoder; import org.apache.logging.log4j.core.util.Transform; import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; @@ -53,15 +56,16 @@ public final class Log4j1XmlLayout extends AbstractStringLayout { @PluginFactory public static Log4j1XmlLayout createLayout( // @formatter:off + @PluginConfiguration Configuration configuration, @PluginAttribute(value = "locationInfo") final boolean locationInfo, @PluginAttribute(value = "properties") final boolean properties // @formatter:on ) { - return new Log4j1XmlLayout(locationInfo, properties); + return new Log4j1XmlLayout(configuration, locationInfo, properties); } - private Log4j1XmlLayout(final boolean locationInfo, final boolean properties) { - super(StandardCharsets.UTF_8); + private Log4j1XmlLayout(final Configuration configuration, final boolean locationInfo, final boolean properties) { + super(configuration, StandardCharsets.UTF_8); this.locationInfo = locationInfo; this.properties = properties; } @@ -76,16 +80,29 @@ 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 = stringBuilderRecycler.acquire(); + try { + formatTo(event, text); + final Encoder stringBuilderEncoder = stringBuilderEncoderRecycler.acquire(); + try { + stringBuilderEncoder.encode(text, destination); + } finally { + stringBuilderEncoderRecycler.release(stringBuilderEncoder); + } + } finally { + stringBuilderRecycler.release(text); + } } @Override public String toSerializable(final LogEvent event) { - final StringBuilder text = getStringBuilder(); - formatTo(event, text); - return text.toString(); + final StringBuilder text = stringBuilderRecycler.acquire(); + try { + formatTo(event, text); + return text.toString(); + } finally { + stringBuilderRecycler.release(text); + } } @SuppressFBWarnings( diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java index 17f3953add2..1a05ea1ffed 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.time.MutableInstant; @@ -71,15 +72,18 @@ static Stream configurations() { public void testSimpleLayout(final String expected, final Facility facility, final boolean header, final boolean facilityPrinting) { final LogEvent logEvent = createLogEvent(); StringLayout appenderLayout = Log4j1SyslogLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setFacility(facility) .setHeader(header) .setFacilityPrinting(facilityPrinting) .build(); assertEquals(expected, appenderLayout.toSerializable(logEvent)); final StringLayout messageLayout = PatternLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setPattern("%m") .build(); appenderLayout = Log4j1SyslogLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setFacility(facility) .setHeader(header) .setFacilityPrinting(facilityPrinting) diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java index 0ef2d37b4b2..630549771e4 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java @@ -17,6 +17,7 @@ package org.apache.log4j.layout; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; @@ -34,7 +35,7 @@ public class Log4j1XmlLayoutTest { @Test public void testWithoutThrown() { - final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(false, true); + final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(new DefaultConfiguration(), false, true); final Log4jLogEvent event = Log4jLogEvent.newBuilder() .setLoggerName("a.B") @@ -55,7 +56,7 @@ public void testWithoutThrown() { @Test public void testWithPropertiesAndLocationInfo() { - final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(true, true); + final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(new DefaultConfiguration(), true, true); final StringMap contextMap = ContextDataFactory.createContextData(2); contextMap.putValue("key1", "value1"); 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 2cf7ed48d94..ba7a5f758c1 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(new ThreadLocalRecyclerFactory(8)); + } + @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/RecyclerFactoriesTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java new file mode 100644 index 00000000000..6a38541003f --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java @@ -0,0 +1,114 @@ +/* + * 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.ArrayDeque; +import java.util.concurrent.ArrayBlockingQueue; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +public class RecyclerFactoriesTest { + + @Test + void DummyRecyclerFactory_should_work() { + final RecyclerFactory actualDummyRecyclerFactory = RecyclerFactories.ofSpec("dummy"); + Assertions + .assertThat(actualDummyRecyclerFactory) + .isSameAs(DummyRecyclerFactory.getInstance()); + } + + @Test + void ThreadLocalRecyclerFactory_should_work() { + final RecyclerFactory actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal"); + Assertions + .assertThat(actualThreadLocalRecyclerFactory) + .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class)) + .extracting(ThreadLocalRecyclerFactory::getCapacity) + .isEqualTo(RecyclerFactories.DEFAULT_QUEUE_CAPACITY); + } + + @Test + void ThreadLocalRecyclerFactory_should_work_with_capacity() { + final RecyclerFactory actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal:capacity=13"); + Assertions + .assertThat(actualThreadLocalRecyclerFactory) + .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class)) + .extracting(ThreadLocalRecyclerFactory::getCapacity) + .isEqualTo(13); + } + + @Test + void QueueingRecyclerFactory_should_work() { + final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue"); + Assertions + .assertThat(actualQueueingRecyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + } + + @Test + void QueueingRecyclerFactory_should_work_with_supplier() { + final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec("queue:supplier=java.util.ArrayDeque.new"); + Assertions + .assertThat(recyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + final QueueingRecyclerFactory queueingRecyclerFactory = (QueueingRecyclerFactory) recyclerFactory; + final Recycler recycler = queueingRecyclerFactory.create(Object::new); + Assertions + .assertThat(recycler) + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; + Assertions + .assertThat(queueingRecycler.getQueue()) + .isInstanceOf(ArrayDeque.class); + } + + @Test + void QueueingRecyclerFactory_should_work_with_capacity() { + final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue:capacity=100"); + Assertions + .assertThat(actualQueueingRecyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + } + + @Test + void QueueingRecyclerFactory_should_work_with_supplier_and_capacity() { + final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec( + "queue:" + + "supplier=java.util.concurrent.ArrayBlockingQueue.new," + + "capacity=100"); + Assertions + .assertThat(recyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + final QueueingRecyclerFactory queueingRecyclerFactory = (QueueingRecyclerFactory) recyclerFactory; + final Recycler recycler = queueingRecyclerFactory.create(Object::new); + Assertions + .assertThat(recycler) + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; + Assertions + .assertThat(queueingRecycler.getQueue()) + .isInstanceOf(ArrayBlockingQueue.class); + final ArrayBlockingQueue queue = (ArrayBlockingQueue) queueingRecycler.getQueue(); + Assertions.assertThat(queue.remainingCapacity()).isEqualTo(100); + + } + +} 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..285205e3e0a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java @@ -0,0 +1,100 @@ +/* + * 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 { + + private static final int CAPACITY = 8; + + private static class RecyclableObject {} + + private Recycler recycler; + + private Queue recyclerQueue; + + @BeforeEach + void setUp() { + recycler = new ThreadLocalRecyclerFactory(CAPACITY).create(RecyclableObject::new); + recyclerQueue = ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue(); + } + + @ParameterizedTest + @IntRangeSource(from = 1, to = CAPACITY, closed = true) + void nested_acquires_should_not_interfere(final int acquisitionCount) { + + // pool should start empty + assertThat(recyclerQueue).isEmpty(); + + final List acquiredObjects = IntStream.range(0, acquisitionCount) + .mapToObj(i -> recycler.acquire()) + .collect(Collectors.toList()); + + // still nothing returned to pool + assertThat(recyclerQueue).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(recyclerQueue).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 nested_acquires_past_max_queue_size_should_discard_extra_releases() { + + assertThat(recyclerQueue).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(recyclerQueue).isEmpty(); + + // don't want any duplicate instances + assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects); + acquiredObjects.forEach(recycler::release); + + // upon return, we should only have `CAPACITY` retained for future use + assertThat(recyclerQueue).hasSize(CAPACITY); + + } + +} diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java similarity index 88% rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java index 1b9a6f79ae7..2bacbe0e70b 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java @@ -14,19 +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; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -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; +package org.apache.logging.log4j.util; + +import java.util.*; + +import org.apache.logging.log4j.util.StringParameterParser.*; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -93,7 +85,7 @@ void test_null_value_1() { void test_null_value_2() { testSuccess( "a,b=c,d=", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.nullValue()); put("b", Values.stringValue("c")); put("d", Values.nullValue()); @@ -104,7 +96,7 @@ void test_null_value_2() { void test_null_value_3() { testSuccess( "a,b=c,d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.nullValue()); put("b", Values.stringValue("c")); put("d", Values.nullValue()); @@ -115,7 +107,7 @@ void test_null_value_3() { void test_null_value_4() { testSuccess( "a,b=\"c,=\\\"\",d=,e=f", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.nullValue()); put("b", Values.doubleQuotedStringValue("c,=\"")); put("d", Values.nullValue()); @@ -127,7 +119,7 @@ void test_null_value_4() { void test_two_pairs() { testSuccess( "a=b,c=d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.stringValue("b")); put("c", Values.stringValue("d")); }}); @@ -144,7 +136,7 @@ void test_quoted_string_01() { void test_quoted_string_02() { testSuccess( "a=\"b\",c=d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("b")); put("c", Values.stringValue("d")); }}); @@ -154,7 +146,7 @@ void test_quoted_string_02() { void test_quoted_string_03() { testSuccess( "a=b,c=\"d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.stringValue("b")); put("c", Values.doubleQuotedStringValue("d")); }}); @@ -164,7 +156,7 @@ void test_quoted_string_03() { void test_quoted_string_04() { testSuccess( "a=\"b\",c=\"d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("b")); put("c", Values.doubleQuotedStringValue("d")); }}); @@ -188,7 +180,7 @@ void test_quoted_string_06() { void test_quoted_string_07() { testSuccess( "a=\"\\\"b\",c=d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b")); put("c", Values.stringValue("d")); }}); @@ -208,7 +200,7 @@ void test_quoted_string_08() { void test_quoted_string_09() { testSuccess( "a=\"\\\"b,\",c=d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b,")); put("c", Values.stringValue("d")); }}); @@ -218,7 +210,7 @@ void test_quoted_string_09() { void test_quoted_string_10() { testSuccess( "a=\"\\\"b\\\",\",c=d", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b\",")); put("c", Values.stringValue("d")); }}); @@ -228,7 +220,7 @@ void test_quoted_string_10() { void test_quoted_string_11() { testSuccess( "a=\"\\\"b\",c=\"d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b")); put("c", Values.doubleQuotedStringValue("d")); }}); @@ -238,7 +230,7 @@ void test_quoted_string_11() { void test_quoted_string_12() { testSuccess( "a=\"\\\"b\\\"\",c=\"d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b\"")); put("c", Values.doubleQuotedStringValue("d")); }}); @@ -258,7 +250,7 @@ void test_quoted_string_13() { void test_quoted_string_14() { testSuccess( "a=\"\\\"b\\\",\",c=\"\\\"d\\\"\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b\",")); put("c", Values.doubleQuotedStringValue("\"d\"")); }}); @@ -268,7 +260,7 @@ void test_quoted_string_14() { void test_quoted_string_15() { testSuccess( "a=\"\\\"b\",c=\",d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b")); put("c", Values.doubleQuotedStringValue(",d")); }}); @@ -278,7 +270,7 @@ void test_quoted_string_15() { void test_quoted_string_16() { testSuccess( "a=\"\\\"b\\\"\",c=\",d\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b\"")); put("c", Values.doubleQuotedStringValue(",d")); }}); @@ -288,7 +280,7 @@ void test_quoted_string_16() { void test_quoted_string_17() { testSuccess( "a=\"\\\"b,\",c=\"\\\"d,\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b,")); put("c", Values.doubleQuotedStringValue("\"d,")); }}); @@ -298,7 +290,7 @@ void test_quoted_string_17() { void test_quoted_string_18() { testSuccess( "a=\"\\\"b\\\",\",c=\"\\\"d\\\",\"", - new LinkedHashMap() {{ + new LinkedHashMap<>() {{ put("a", Values.doubleQuotedStringValue("\"b\",")); put("c", Values.doubleQuotedStringValue("\"d\",")); }}); diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java index 08d4b31b478..f77a5a0c5bc 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java @@ -54,15 +54,6 @@ public void testEMPTY() { assertEquals(0, Strings.EMPTY.length()); } - @Test - public void testConcat() { - assertEquals("ab", Strings.concat("a", "b")); - assertEquals("a", Strings.concat("a", "")); - assertEquals("a", Strings.concat("a", null)); - assertEquals("b", Strings.concat("", "b")); - assertEquals("b", Strings.concat(null, "b")); - } - @Test public void testJoin() { assertNull(Strings.join((Iterable) null, '.')); diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml index 1885edaf9ef..835f836db6a 100644 --- a/log4j-api/pom.xml +++ b/log4j-api/pom.xml @@ -55,5 +55,10 @@ org.osgi.resource provided + + org.jctools + jctools-core + true + 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 f11b9176144..f7a19f7fe06 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 @@ -21,24 +21,21 @@ import org.apache.logging.log4j.BridgeAware; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogBuilder; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LambdaUtil; -import org.apache.logging.log4j.util.StackLocatorUtil; -import org.apache.logging.log4j.util.Strings; -import org.apache.logging.log4j.util.Supplier; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerAware; +import org.apache.logging.log4j.util.*; /** * Collects data for a log event and then logs it. This class should be considered private. */ -public class DefaultLogBuilder implements BridgeAware, LogBuilder { +@InternalApi +public class DefaultLogBuilder implements BridgeAware, LogBuilder, RecyclerAware { private static final String FQCN = DefaultLogBuilder.class.getName(); - private static final Logger LOGGER = StatusLogger.getLogger(); private static final Message EMPTY_MESSAGE = new SimpleMessage(Strings.EMPTY); private ExtendedLogger logger; @@ -46,15 +43,12 @@ 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; + private Recycler recycler; public DefaultLogBuilder(final ExtendedLogger logger, final Level level) { this.logger = logger; this.level = level; - this.threadId = Thread.currentThread().getId(); - this.inUse = level != null; } public DefaultLogBuilder() { @@ -77,7 +71,6 @@ public LogBuilder reset(final ExtendedLogger logger, final Level level) { this.marker = null; this.throwable = null; this.location = null; - this.inUse = true; return this; } @@ -105,13 +98,9 @@ public LogBuilder withLocation(final StackTraceElement location) { return this; } - public boolean isInUse() { - return inUse; - } - @Override public void log(final Message message) { - if (isValid() && isEnabled(message)) { + if (isEnabled(message)) { logMessage(message); } } @@ -119,7 +108,7 @@ public void log(final Message message) { @Override public Message logAndGet(final Supplier messageSupplier) { Message message = null; - if (isValid() && isEnabled(message = messageSupplier.get())) { + if (isEnabled(message = messageSupplier.get())) { logMessage(message); } return message; @@ -127,21 +116,21 @@ public Message logAndGet(final Supplier messageSupplier) { @Override public void log(final CharSequence message) { - if (isValid() && isEnabled(message)) { + if (isEnabled(message)) { logMessage(logger.getMessageFactory().newMessage(message)); } } @Override public void log(final String message) { - if (isValid() && isEnabled(message)) { + if (isEnabled(message)) { logMessage(logger.getMessageFactory().newMessage(message)); } } @Override public void log(final String message, final Object... params) { - if (isValid() && isEnabled(message, params)) { + if (isEnabled(message, params)) { logMessage(logger.getMessageFactory().newMessage(message, params)); } } @@ -149,7 +138,7 @@ public void log(final String message, final Object... params) { @Override public void log(final String message, final Supplier... params) { final Object[] objs; - if (isValid() && isEnabled(message, objs = LambdaUtil.getAll(params))) { + if (isEnabled(message, objs = LambdaUtil.getAll(params))) { logMessage(logger.getMessageFactory().newMessage(message, objs)); } } @@ -161,56 +150,56 @@ public void log(final Supplier messageSupplier) { @Override public void log(final Object message) { - if (isValid() && isEnabled(message)) { + if (isEnabled(message)) { logMessage(logger.getMessageFactory().newMessage(message)); } } @Override public void log(final String message, final Object p0) { - if (isValid() && isEnabled(message, p0)) { + if (isEnabled(message, p0)) { logMessage(logger.getMessageFactory().newMessage(message, p0)); } } @Override public void log(final String message, final Object p0, final Object p1) { - if (isValid() && isEnabled(message, p0, p1)) { + if (isEnabled(message, p0, p1)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); } } @Override public void log(final String message, final Object p0, final Object p1, final Object p2) { - if (isValid() && isEnabled(message, p0, p1, p2)) { + if (isEnabled(message, p0, p1, p2)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); } } @Override public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { - if (isValid() && isEnabled(message, p0, p1, p2, p3)) { + if (isEnabled(message, p0, p1, p2, p3)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); } } @Override public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4)) { + if (isEnabled(message, p0, p1, p2, p3, p4)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); } } @Override public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5)) { + if (isEnabled(message, p0, p1, p2, p3, p4, p5)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); } } @Override public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6)) { + if (isEnabled(message, p0, p1, p2, p3, p4, p5, p6)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); } } @@ -218,7 +207,7 @@ public void log(final String message, final Object p0, final Object p1, final Ob @Override public void log(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) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7)) { + if (isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); } } @@ -226,7 +215,7 @@ public void log(final String message, final Object p0, final Object p1, final Ob @Override public void log(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) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + if (isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); } } @@ -234,14 +223,14 @@ public void log(final String message, final Object p0, final Object p1, final Ob @Override public void log(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) { - if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + if (isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); } } @Override public void log() { - if (isValid() && isEnabled(EMPTY_MESSAGE)) { + if (isEnabled(EMPTY_MESSAGE)) { logMessage(EMPTY_MESSAGE); } } @@ -250,22 +239,14 @@ private void logMessage(final Message message) { try { logger.logMessage(level, marker, fqcn, location, message, throwable); } finally { - inUse = false; - } - } - - private boolean isValid() { - if (!inUse) { - LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", - StackLocatorUtil.getCallerClass(2)); - return false ; + this.level = null; + this.marker = null; + this.throwable = null; + this.location = null; + if (recycler != null) { + recycler.release(this); + } } - if (this.threadId != Thread.currentThread().getId()) { - LOGGER.warn("LogBuilder can only be used on the owning thread. {}", - StackLocatorUtil.getCallerClass(2)); - return false; - } - return true; } protected boolean isEnabled(Message message) { @@ -350,4 +331,10 @@ protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Obj ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, throwable) : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } + + @Override + public void setRecycler(Recycler recycler) { + this.recycler = recycler; + } + } 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 0bcb11573a0..b1e5b207294 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 @@ -16,8 +16,12 @@ */ package org.apache.logging.log4j.message; +import org.apache.logging.log4j.spi.Recycler; + /** * 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 +240,13 @@ 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 Recycler + */ + default void recycle(Message message) {} + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java index 4d11b59732b..391bee004d4 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java @@ -19,7 +19,8 @@ import java.util.Arrays; import java.util.Objects; -import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.spi.Recycler; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.StringBuilders; @@ -65,8 +66,12 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable { */ public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX; - // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay - private static final ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); + private static final Recycler STRING_BUILDER_RECYCLER = LoggingSystem.getRecyclerFactory().create( + () -> new StringBuilder(DEFAULT_STRING_BUILDER_SIZE), + stringBuilder -> { + StringBuilders.trimToMaxSize(stringBuilder, DEFAULT_STRING_BUILDER_SIZE); + stringBuilder.setLength(0); + }); private String messagePattern; private final Object[] argArray; @@ -181,24 +186,17 @@ public Throwable getThrowable() { @Override public String getFormattedMessage() { if (formattedMessage == null) { - final StringBuilder buffer = getThreadLocalStringBuilder(); - formatTo(buffer); - formattedMessage = buffer.toString(); - StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + final StringBuilder buffer = STRING_BUILDER_RECYCLER.acquire(); + try { + formatTo(buffer); + formattedMessage = buffer.toString(); + } finally { + STRING_BUILDER_RECYCLER.release(buffer); + } } return formattedMessage; } - private static StringBuilder getThreadLocalStringBuilder() { - StringBuilder buffer = threadLocalStringBuilder.get(); - if (buffer == null) { - buffer = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); - threadLocalStringBuilder.set(buffer); - } - buffer.setLength(0); - return buffer; - } - @Override public void formatTo(final StringBuilder buffer) { if (formattedMessage != null) { 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 927a9abe5a5..8abab0c8514 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,28 @@ public final class ReusableMessageFactory implements MessageFactory { */ public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory(); - private final ThreadLocal threadLocalParameterized = new ThreadLocal<>(); - private final ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>(); - private 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(); + this(LoggingSystem.getRecyclerFactory()); } - private ReusableParameterizedMessage getParameterized() { - ReusableParameterizedMessage result = threadLocalParameterized.get(); - if (result == null) { - result = new ReusableParameterizedMessage(); - threadLocalParameterized.set(result); - } - return result.reserved ? new ReusableParameterizedMessage().reserve() : result.reserve(); - } - - private ReusableSimpleMessage getSimple() { - ReusableSimpleMessage result = threadLocalSimpleMessage.get(); - if (result == null) { - result = new ReusableSimpleMessage(); - threadLocalSimpleMessage.set(result); - } - return result; - } - - private 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, + ReusableParameterizedMessage::clear); + simpleMessageRecycler = recyclerFactory.create( + ReusableSimpleMessage::new, + ReusableSimpleMessage::clear); + objectMessageRecycler = recyclerFactory.create( + ReusableObjectMessage::new, + ReusableObjectMessage::clear); } /** @@ -89,9 +80,24 @@ public static void release(final Message message) { // LOG4J2-1583 } } + @Override + public void recycle(final Message message) { + if (message instanceof ReusableMessage) { + ((ReusableMessage) message).clear(); + } + // 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 82073e2b87f..b63680837f5 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,28 @@ 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; /** * 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(); + int capacity = Math.max(MIN_BUILDER_SIZE, Math.multiplyExact(currentPatternLength, 2)); + return new StringBuilder(capacity); + }, + buffer -> { + StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + buffer.setLength(0); + } + ); } private Object[] getTrimmedParams() { @@ -299,25 +317,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 +335,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 +345,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 fd1b975c24d..cfd92255cf6 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 @@ -16,26 +16,11 @@ */ package org.apache.logging.log4j.spi; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogBuilder; -import org.apache.logging.log4j.LoggingException; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.*; import org.apache.logging.log4j.internal.DefaultLogBuilder; -import org.apache.logging.log4j.message.EntryMessage; -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.message.*; 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; -import org.apache.logging.log4j.util.StackLocatorUtil; -import org.apache.logging.log4j.util.Supplier; +import org.apache.logging.log4j.util.*; /** * Base implementation of a Logger. It is highly recommended that any Logger implementation extend this class. @@ -84,17 +69,13 @@ 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 - private static final ThreadLocal logBuilder = ThreadLocal.withInitial(DefaultLogBuilder::new); - + private final Recycler recycler; /** * Creates a new logger named after this class (or subclass). */ public AbstractLogger() { - final String canonicalName = getClass().getCanonicalName(); - this.name = canonicalName != null ? canonicalName : getClass().getName(); - this.messageFactory = LoggingSystem.getMessageFactory(); - this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); + this(null, null, null, null); } /** @@ -103,7 +84,7 @@ public AbstractLogger() { * @param name the logger name */ public AbstractLogger(final String name) { - this(name, LoggingSystem.getMessageFactory()); + this(name, LoggingSystem.getMessageFactory(), null, null); } /** @@ -113,9 +94,31 @@ public AbstractLogger(final String name) { * @param messageFactory the message factory, if null then use the default message factory. */ public AbstractLogger(final String name, final MessageFactory messageFactory) { - this.name = name; - this.messageFactory = messageFactory == null ? LoggingSystem.getMessageFactory() : messageFactory; - this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); + this(name, messageFactory, null, null); + } + + private AbstractLogger( + final String name, + final MessageFactory messageFactory, + final FlowMessageFactory flowMessageFactory, + final RecyclerFactory recyclerFactory) { + this.name = createNameFromClass(name, getClass()); + this.messageFactory = messageFactory != null ? messageFactory : LoggingSystem.getMessageFactory(); + this.flowMessageFactory = flowMessageFactory != null + ? flowMessageFactory + : LoggingSystem.getFlowMessageFactory(); + final RecyclerFactory effectiveRecyclerFactory = recyclerFactory != null + ? recyclerFactory + : LoggingSystem.getRecyclerFactory(); + this.recycler = effectiveRecyclerFactory.create(DefaultLogBuilder::new); + } + + private static String createNameFromClass(String name, Class clazz) { + if (name != null) { + return name; + } + final String canonicalName = clazz.getCanonicalName(); + return canonicalName != null ? canonicalName : clazz.getName(); } /** @@ -1942,7 +1945,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); } } @@ -1976,7 +1979,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); } } @@ -2762,13 +2765,9 @@ public LogBuilder atLevel(final Level level) { * * @since 2.20.0 */ - protected LogBuilder getLogBuilder(final Level level) { - if (Constants.isThreadLocalsEnabled()) { - final DefaultLogBuilder builder = logBuilder.get(); - if (!builder.isInUse()) { - return builder.reset(this, level); - } - } - return new DefaultLogBuilder(this, level); + protected LogBuilder getLogBuilder(Level level) { + DefaultLogBuilder builder = recycler.acquire(); + return builder.reset(this, level); } + } 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/AbstractRecycler.java similarity index 65% 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/AbstractRecycler.java index 4e29e3990b8..7c8fac064ef 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/AbstractRecycler.java @@ -14,24 +14,26 @@ * 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 { +public abstract class AbstractRecycler implements Recycler { private final Supplier supplier; - public DummyRecycler(final Supplier supplier) { + public AbstractRecycler(final Supplier supplier) { this.supplier = supplier; } - @Override - public V acquire() { - return supplier.get(); + protected final V createInstance() { + final V instance = supplier.get(); + if (instance instanceof RecyclerAware) { + @SuppressWarnings("unchecked") + RecyclerAware recyclerAware = (RecyclerAware) instance; + recyclerAware.setRecycler(this); + } + return instance; } - @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/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java similarity index 64% 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 ce222e1659c..a9995f9c3e5 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,18 @@ * 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; +import static java.util.Objects.requireNonNull; + +/** + * Recycler strategy which doesn't recycle anything; all instances are freshly created. + * + * @since 3.0.0 + */ public final class DummyRecyclerFactory implements RecyclerFactory { private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory(); @@ -30,10 +37,25 @@ public static DummyRecyclerFactory getInstance() { } @Override - public Recycler create( - final Supplier supplier, - final Consumer cleaner) { + public Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); return new DummyRecycler<>(supplier); } + private static class DummyRecycler extends AbstractRecycler { + + private DummyRecycler(final Supplier supplier) { + super(supplier); + } + + @Override + public V acquire() { + return createInstance(); + } + + @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 66ad3f26810..2bfa9debb68 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 @@ -102,6 +102,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 @@ -181,6 +182,10 @@ public void setThreadContextStackFactory(final Supplier thre threadContextStackFactoryLazy.set(threadContextStackFactory); } + public void setRecyclerFactory(final RecyclerFactory factory) { + recyclerFactoryLazy.set(factory); + } + /** * Gets the LoggingSystem instance. */ @@ -218,6 +223,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..6a1ffb12644 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.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.Queue; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.logging.log4j.util.QueueFactory; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link RecyclerFactory} pooling objects in a queue created using the provided {@link QueueFactory}. + */ +public class QueueingRecyclerFactory implements RecyclerFactory { + + private final QueueFactory queueFactory; + + public QueueingRecyclerFactory(final QueueFactory queueFactory) { + this.queueFactory = requireNonNull(queueFactory, "queueFactory"); + } + + @Override + public Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); + requireNonNull(cleaner, "cleaner"); + final Queue queue = queueFactory.create(); + return new QueueingRecycler<>(supplier, cleaner, queue); + } + + // Visible for tests + static class QueueingRecycler extends AbstractRecycler { + + private final Consumer cleaner; + + private final Queue queue; + + private QueueingRecycler( + final Supplier supplier, + final Consumer cleaner, + final Queue queue) { + super(supplier); + this.cleaner = cleaner; + this.queue = queue; + } + + // Visible for tests + Queue getQueue() { + return queue; + } + + @Override + public V acquire() { + final V value = queue.poll(); + return value != null ? value : createInstance(); + } + + @Override + public void release(final V value) { + requireNonNull(value, "value"); + cleaner.accept(value); + queue.offer(value); + } + + } + +} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.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/RecyclerFactoryConverter.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java index c0a61976494..11dc8793fbd 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java @@ -14,22 +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; - -import org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.plugins.convert.TypeConverter; -import org.apache.logging.log4j.plugins.convert.TypeConverters; +package org.apache.logging.log4j.spi; /** - * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter. + * Strategy for recycling objects. This is primarily useful for heavyweight objects and buffers. + * + * @param the recyclable type + * @since 3.0.0 */ -@TypeConverters -@Plugin -public final class RecyclerFactoryConverter implements TypeConverter { +public interface Recycler { + + /** + * 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 RecyclerFactory convert(final String recyclerFactorySpec) { - return RecyclerFactories.ofSpec(recyclerFactorySpec); - } + /** + * 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-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/spi/RecyclerAware.java similarity index 77% 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/spi/RecyclerAware.java index 2f20e4efccc..42e4d721637 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/spi/RecyclerAware.java @@ -14,12 +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; - -public interface Recycler { - - V acquire(); - - void release(V value); +package org.apache.logging.log4j.spi; +/** + * Interface implemented by classes that need to interact with the {@link Recycler} that created them. + */ +public interface RecyclerAware { + void setRecycler(Recycler recycler); } 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..97c61c94c7d --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java @@ -0,0 +1,131 @@ +/* + * 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.QueueFactories; +import org.apache.logging.log4j.util.QueueFactory; +import org.apache.logging.log4j.util.StringParameterParser; + +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + +public final class RecyclerFactories { + + // Visible for testing + static final int DEFAULT_QUEUE_CAPACITY = Math.max( + 2 * Runtime.getRuntime().availableProcessors() + 1, + 8); + + private RecyclerFactories() {} + + public static RecyclerFactory getDefault() { + return isThreadLocalsEnabled() + ? new ThreadLocalRecyclerFactory(DEFAULT_QUEUE_CAPACITY) + : new QueueingRecyclerFactory(QueueFactories.MPMC.factory(DEFAULT_QUEUE_CAPACITY)); + } + + 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.startsWith("threadLocal")) { + return readThreadLocalRecyclerFactory(recyclerFactorySpec); + } + + // Is a queueing factory requested? + else if (recyclerFactorySpec.startsWith("queue")) { + return readQueueingRecyclerFactory(recyclerFactorySpec); + } + + // Bogus input, bail out. + else { + throw new IllegalArgumentException( + "invalid recycler factory: " + recyclerFactorySpec); + } + + } + + private static RecyclerFactory readThreadLocalRecyclerFactory(final String recyclerFactorySpec) { + + // Parse the spec + final String queueFactorySpec = recyclerFactorySpec.substring( + "threadLocal".length() + (recyclerFactorySpec.startsWith("threadLocal:") ? 1 : 0)); + final Map parsedValues = + StringParameterParser.parse(queueFactorySpec, Set.of("capacity")); + + // Read the capacity + final int capacity = readQueueCapacity(queueFactorySpec, parsedValues); + + // Execute the read spec + return new ThreadLocalRecyclerFactory(capacity); + + } + + private static RecyclerFactory readQueueingRecyclerFactory(final String recyclerFactorySpec) { + + // 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 int capacity = readQueueCapacity(queueFactorySpec, parsedValues); + + // Read the supplier path + final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); + final String supplierPath = supplierValue == null || supplierValue instanceof StringParameterParser.NullValue + ? null + : supplierValue.toString(); + + // Execute the read spec + final QueueFactory queueFactory = supplierPath != null + ? QueueFactories.createQueueFactory(supplierPath, capacity) + : QueueFactories.MPMC.factory(capacity); + return new QueueingRecyclerFactory(queueFactory); + + } + + private static int readQueueCapacity( + final String factorySpec, + final Map parsedValues) { + final StringParameterParser.Value capacityValue = parsedValues.get("capacity"); + if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) { + return DEFAULT_QUEUE_CAPACITY; + } else { + try { + return Integer.parseInt(capacityValue.toString()); + } catch (final NumberFormatException error) { + throw new IllegalArgumentException( + "failed reading `capacity` in recycler factory: " + factorySpec, + error); + } + } + } + +} 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..73566be4e8e --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java @@ -0,0 +1,59 @@ +/* + * 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, ignored -> {}); + } + + /** + * 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 + */ + Recycler create(Supplier supplier, Consumer cleaner); + +} 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..4f0e8f3751a --- /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.QueueFactories; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link RecyclerFactory} pooling objects in a queue stored in a {@link ThreadLocal}. + *

+ * 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. + * For such use cases, see {@link QueueingRecyclerFactory}. + *

+ * + * @since 3.0.0 + */ +public class ThreadLocalRecyclerFactory implements RecyclerFactory { + + /** + * Maximum number of objects retained per thread. + *

+ * This allows to acquire 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. + *

+ */ + private final int capacity; + + public ThreadLocalRecyclerFactory(int capacity) { + if (capacity < 1) { + throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity); + } + this.capacity = capacity; + } + + /** + * @return maximum number of objects retained per thread in recyclers created + */ + public int getCapacity() { + return capacity; + } + + @Override + public Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); + requireNonNull(cleaner, "cleaner"); + return new ThreadLocalRecycler<>(supplier, cleaner, capacity); + } + + // Visible for testing + static class ThreadLocalRecycler extends AbstractRecycler { + + private final Consumer cleaner; + + private final ThreadLocal> holder; + + private ThreadLocalRecycler( + final Supplier supplier, + final Consumer cleaner, + final int capacity) { + super(supplier); + this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(capacity)); + this.cleaner = cleaner; + } + + @Override + public V acquire() { + final Queue queue = holder.get(); + final V value = queue.poll(); + return value != null ? value : createInstance(); + } + + @Override + public void release(final V value) { + requireNonNull(value, "value"); + cleaner.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 5a57841814b..2031d327792 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.LoggingSystemProperty; import org.apache.logging.log4j.util.LowLevelLogUtil; +import org.apache.logging.log4j.util.QueueFactories; /** * 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 = QueueFactories.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-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java new file mode 100644 index 00000000000..eb84cb481f9 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java @@ -0,0 +1,234 @@ +/* + * 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. + *

+ * Implementations provided by JCTools will be preferred, if available at runtime. + * Otherwise, {@link ArrayBlockingQueue} will be used. + *

+ * + * @since 3.0.0 + */ +@InternalApi +public enum QueueFactories { + + /** + * Provides a bounded queue for single-producer/single-consumer usage. + */ + SPSC(Lazy.lazy(JCToolsQueueFactory.SPSC::load)), + + /** + * Provides a bounded queue for multi-producer/single-consumer usage. + */ + MPSC(Lazy.lazy(JCToolsQueueFactory.MPSC::load)), + + /** + * Provides a bounded queue for single-producer/multi-consumer usage. + */ + SPMC(Lazy.lazy(JCToolsQueueFactory.SPMC::load)), + + /** + * Provides a bounded queue for multi-producer/multi-consumer usage. + */ + MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load)); + + private final Lazy queueFactory; + + QueueFactories(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); + } + + /** + * Creates a {@link QueueFactory} producing queues of provided capacity from the provided supplier. + *

+ * A supplier path must be formatted as follows: + *

    + *
  • {@code .new} – the class constructor accepting a single {@code int} argument (denoting the capacity) will be used (e.g., {@code org.jctools.queues.MpmcArrayQueue.new})
  • + *
  • {@code .} – the static factory method accepting a single {@code int} argument (denoting the capacity) will be used (e.g., {@code com.acme.Queues.createBoundedQueue})
  • + *
+ *

+ * + * @param supplierPath a queue supplier path (e.g., {@code org.jctools.queues.MpmcArrayQueue.new}, {@code com.acme.Queues.createBoundedQueue}) + * @param capacity the capacity that will be passed to the queue supplier + * @return a new {@link QueueFactory} instance + */ + public static QueueFactory createQueueFactory(final String supplierPath, final int capacity) { + final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.'); + if (supplierPathSplitterIndex < 0) { + final String message = String.format("invalid queue factory supplier path: `%s`", supplierPath); + throw new IllegalArgumentException(message); + } + 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) { + final String message = String.format( + "failed to create the queue factory using the supplier path `%s`", supplierPath); + throw new RuntimeException(message, error); + } + } + + private static final 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); + } + + } + + @FunctionalInterface + private interface BoundedQueueFactory { + + Queue create(final int capacity); + + } + + private static final class ArrayBlockingQueueFactory implements BoundedQueueFactory { + + private static final ArrayBlockingQueueFactory INSTANCE = new ArrayBlockingQueueFactory(); + + private ArrayBlockingQueueFactory() {} + + @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); + } + }; + + private BoundedQueueFactory load() { + try { + // Test with a large enough capacity to avoid any `IllegalArgumentExceptions` from trivial queues + create(16); + return this; + } catch (final LinkageError ignored) { + return ArrayBlockingQueueFactory.INSTANCE; + } + } + + } + + private static final 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 error) { + throw new RuntimeException("queue construction failure", error); + } + } + + } + + private static final 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 error) { + throw new RuntimeException("queue construction failure", error); + } + } + + } + +} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java similarity index 69% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java index 74a6f83239f..f4b54f0f74f 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java @@ -14,18 +14,19 @@ * 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; -import java.util.function.Consumer; -import java.util.function.Supplier; +import java.util.Queue; +/** + * A {@link Queue} factory contract. + * + * @see QueueFactories + * @since 3.0.0 + */ @FunctionalInterface -public interface RecyclerFactory { - - default Recycler create(final Supplier supplier) { - return create(supplier, ignored -> {}); - } +public interface QueueFactory { - Recycler create(Supplier supplier, Consumer cleaner); + Queue create(); } 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 96% 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 37f69f1c8b2..23d87c765ad 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,17 +14,18 @@ * 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; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; -import org.apache.logging.log4j.util.Strings; - +/** + * Utility class for parsing string-formatted parameters, e.g., {@code queue:supplier=com.acme.FastestQueue.new,capacity=42}. + *

+ * See the associated test class for possible combinations and double-, single-quote handling. + *

+ */ +@InternalApi public final class StringParameterParser { private StringParameterParser() {} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java index 6c02b6ba06c..cb630d4d8f3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java @@ -29,8 +29,6 @@ @InternalApi public final class Strings { - private static final ThreadLocal tempStr = ThreadLocal.withInitial(StringBuilder::new); - /** * The empty string. */ @@ -193,6 +191,26 @@ public static String quote(final String str) { return Chars.QUOTE + str + Chars.QUOTE; } + /** + * Shorthand for {@code str.toLowerCase(Locale.ROOT);} + * @param str The string to lower case. + * @return a new string + * @see String#toLowerCase(Locale) + */ + public static String toRootLowerCase(final String str) { + return str.toLowerCase(Locale.ROOT); + } + + /** + * Shorthand for {@code str.toUpperCase(Locale.ROOT);} + * @param str The string to upper case. + * @return a new string + * @see String#toUpperCase(Locale) + */ + public static String toRootUpperCase(final String str) { + return str.toUpperCase(Locale.ROOT); + } + /** *

* Removes control characters (char <= 32) from both ends of this String returning {@code null} if the String is @@ -303,67 +321,4 @@ public static String[] splitList(final String string) { return string != null ? string.split(COMMA_DELIMITED_RE) : new String[0]; } - /** - * Shorthand for {@code str.toLowerCase(Locale.ROOT);} - * @param str The string to upper case. - * @return a new string - * @see String#toLowerCase(Locale) - */ - public static String toRootLowerCase(final String str) { - return str.toLowerCase(Locale.ROOT); - } - - /** - * Shorthand for {@code str.toUpperCase(Locale.ROOT);} - * @param str The string to lower case. - * @return a new string - * @see String#toLowerCase(Locale) - */ - public static String toRootUpperCase(final String str) { - return str.toUpperCase(Locale.ROOT); - } - - /** - * Concatenates 2 Strings without allocation. - * @param str1 the first string. - * @param str2 the second string. - * @return the concatenated String. - */ - public static String concat(final String str1, final String str2) { - if (isEmpty(str1)) { - return str2; - } else if (isEmpty(str2)) { - return str1; - } - final StringBuilder sb = tempStr.get(); - try { - return sb.append(str1).append(str2).toString(); - } finally { - sb.setLength(0); - } - } - - /** - * Creates a new string repeating given {@code str} {@code count} times. - * @param str input string - * @param count the repetition count - * @return the new string - * @throws IllegalArgumentException if either {@code str} is null or {@code count} is negative - */ - public static String repeat(final String str, final int count) { - Objects.requireNonNull(str, "str"); - if (count < 0) { - throw new IllegalArgumentException("count"); - } - final StringBuilder sb = tempStr.get(); - try { - for (int index = 0; index < count; index++) { - sb.append(str); - } - return sb.toString(); - } finally { - sb.setLength(0); - } - } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java index 8adfe31d5dc..7b2c8234447 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.SyslogAppender.Builder; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.SyslogLayout; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Protocol; @@ -34,6 +35,7 @@ protected Facility getExpectedFacility() { protected Builder newSyslogAppenderBuilder(final Protocol protocol, final TlsSyslogMessageFormat format, final boolean newLine, final int port) { final Layout layout = SyslogLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setFacility(Facility.LOCAL3) .setIncludeNewLine(true) .build(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java index ba51d5cc671..117f79dd9fc 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java @@ -20,6 +20,7 @@ import java.net.SocketException; import org.apache.logging.log4j.core.appender.SyslogAppender.Builder; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Protocol; import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; @@ -119,6 +120,7 @@ protected Builder newSyslogAppenderBuilder(final Protocol protocol, final Tls final boolean newLine, final int port) { // @formatter:off return SyslogAppender.newSyslogAppenderBuilder() + .setConfiguration(new DefaultConfiguration()) .setHost("localhost") .setPort(port) .setProtocol(protocol) 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 3843ef6fc0f..4c276a663fb 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/async/perftest/RunConversant.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java index ae683776941..d724aad34ef 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java @@ -25,6 +25,6 @@ public class RunConversant extends AbstractRunQueue { @Override BlockingQueue createQueue(final int capacity) { - return DisruptorBlockingQueueFactory.createFactory(SpinPolicy.SPINNING).create(capacity); + return DisruptorBlockingQueueFactory.createFactory(SpinPolicy.SPINNING).create(capacity); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java index c329a4ef43f..0796418eb05 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java @@ -25,7 +25,7 @@ public class RunJCTools extends AbstractRunQueue { @Override BlockingQueue createQueue(final int capacity) { - return JCToolsBlockingQueueFactory.createFactory(WaitStrategy.SPIN).create(capacity); + return JCToolsBlockingQueueFactory.createFactory(WaitStrategy.SPIN).create(capacity); } 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 29bb7cb67b6..34aaa095622 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,40 +33,39 @@ public class ReusableLogEventFactoryTest { private final ConfigurableInstanceFactory instanceFactory = DI.createInitializedFactory(); + private final ReusableLogEventFactory factory = instanceFactory.getInstance(ReusableLogEventFactory.class); + @Test - public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { - final ReusableLogEventFactory factory = instanceFactory.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); + public void testCreateEventReturnsDifferentInstanceIfNotReleased() { + 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 = instanceFactory.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); + public void testCreateEventReturnsSameInstance() { + 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 = instanceFactory.getInstance(ReusableLogEventFactory.class); - final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + public void testCreateEventOverwritesFields() { + 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"); @@ -77,26 +76,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 = instanceFactory.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(); @@ -117,19 +114,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 = instanceFactory.getInstance(ReusableLogEventFactory.class); - final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null); + public void testCreateEventInitFieldsProperly() { + 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 875aa0b280a..d34a67b056e 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 @@ -19,9 +19,11 @@ import java.nio.charset.Charset; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.DefaultConfiguration; 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. @@ -32,11 +34,7 @@ static class ConcreteStringLayout extends AbstractStringLayout { public static int MAX_STRING_BUILDER_SIZE = AbstractStringLayout.MAX_STRING_BUILDER_SIZE; public ConcreteStringLayout() { - super(Charset.defaultCharset()); - } - - public static StringBuilder getStringBuilder() { - return AbstractStringLayout.getStringBuilder(); + super(new DefaultConfiguration(), Charset.defaultCharset()); } @Override @@ -46,36 +44,51 @@ public String toSerializable(final LogEvent event) { } @Test - public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { - final StringBuilder sb = ConcreteStringLayout.getStringBuilder(); + public void testGetStringBuilderCapacityRestrictedToMax() { + + final ConcreteStringLayout layout = new ConcreteStringLayout(); + final StringBuilder sb = layout.stringBuilderRecycler.acquire(); final int initialCapacity = sb.capacity(); - assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); + try { + assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); - final int SMALL = 100; - final String smallMessage = new String(new char[SMALL]); - sb.append(smallMessage); - assertEquals(initialCapacity, sb.capacity(), "capacity not grown"); - assertEquals(SMALL, sb.length(), "length=msg length"); + final int SMALL = 100; + final String smallMessage = new String(new char[SMALL]); + sb.append(smallMessage); + assertEquals(initialCapacity, sb.capacity(), "capacity not grown"); + assertEquals(SMALL, sb.length(), "length=msg length"); + } finally { + layout.stringBuilderRecycler.release(sb); + } - final StringBuilder sb2 = ConcreteStringLayout.getStringBuilder(); - assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); - assertEquals(0, sb2.length(), "empty, ready for use"); + final StringBuilder sb2 = layout.stringBuilderRecycler.acquire(); + try { + assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); + assertEquals(0, sb2.length(), "empty, ready for use"); - final int LARGE = ConcreteStringLayout.MAX_STRING_BUILDER_SIZE * 2; - final String largeMessage = new String(new char[LARGE]); - sb2.append(largeMessage); - assertTrue(sb2.capacity() >= LARGE, "capacity grown to fit msg length"); - assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, - "capacity is now greater than max length"); - assertEquals(LARGE, sb2.length(), "length=msg length"); - 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"); + final int LARGE = ConcreteStringLayout.MAX_STRING_BUILDER_SIZE * 2; + final String largeMessage = new String(new char[LARGE]); + sb2.append(largeMessage); + assertTrue(sb2.capacity() >= LARGE, "capacity grown to fit msg length"); + assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, + "capacity is now greater than max length"); + assertEquals(LARGE, sb2.length(), "length=msg length"); + 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"); + } finally { + layout.stringBuilderRecycler.release(sb2); + } + + final StringBuilder sb3 = layout.stringBuilderRecycler.acquire(); + try { + assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(), + "capacity, trimmed to MAX_STRING_BUILDER_SIZE"); + assertEquals(0, sb3.length(), "empty, ready for use"); + } finally { + layout.stringBuilderRecycler.release(sb3); + } - final StringBuilder sb3 = ConcreteStringLayout.getStringBuilder(); - 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-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java index b02bfa7ac8b..87cf3430fb8 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java @@ -31,6 +31,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.GelfLayout.CompressionType; import org.apache.logging.log4j.core.lookup.JavaLookup; import org.apache.logging.log4j.core.test.BasicConfigurationFactory; @@ -284,8 +285,9 @@ public void testFormatTimestamp() { private void testRequiresLocation(final String messagePattern, final Boolean requiresLocation) { final GelfLayout layout = GelfLayout.newBuilder() - .setMessagePattern(messagePattern) - .build(); + .setConfiguration(new DefaultConfiguration()) + .setMessagePattern(messagePattern) + .build(); assertEquals(layout.requiresLocation(), requiresLocation); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java index 961fff12938..2084e18f274 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java @@ -29,11 +29,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.AbstractLogEvent; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.test.BasicConfigurationFactory; import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.test.junit.ConfigurationFactoryType; @@ -95,6 +92,7 @@ public void testDefaultContentType() { @Test public void testContentType() { final HtmlLayout layout = HtmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setContentType("text/html; charset=UTF-16") .build(); assertEquals("text/html; charset=UTF-16", layout.getContentType()); @@ -128,6 +126,7 @@ private void testLayout(final boolean includeLocation) throws Exception { } // set up appender final HtmlLayout layout = HtmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(includeLocation) .build(); final ListAppender appender = new ListAppender("List", null, layout, true, false); @@ -181,7 +180,7 @@ private void testLayout(final boolean includeLocation) throws Exception { @Test public void testLayoutWithoutDataPattern() { - final HtmlLayout layout = HtmlLayout.newBuilder().build(); + final HtmlLayout layout = HtmlLayout.newBuilder().setConfiguration(new DefaultConfiguration()).build(); final MyLogEvent event = new MyLogEvent(); final String actual = getDateLine(layout.toSerializable(event)); @@ -192,7 +191,7 @@ public void testLayoutWithoutDataPattern() { @Test public void testLayoutWithDatePatternJvmElapseTime() { - final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("JVM_ELAPSE_TIME").build(); + final HtmlLayout layout = HtmlLayout.newBuilder().setConfiguration(new DefaultConfiguration()).setDatePattern("JVM_ELAPSE_TIME").build(); final MyLogEvent event = new MyLogEvent(); final String actual = getDateLine(layout.toSerializable(event)); @@ -203,7 +202,7 @@ public void testLayoutWithDatePatternJvmElapseTime() { @Test public void testLayoutWithDatePatternUnix() { - final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX").build(); + final HtmlLayout layout = HtmlLayout.newBuilder().setConfiguration(new DefaultConfiguration()).setDatePattern("UNIX").build(); final MyLogEvent event = new MyLogEvent(); final String actual = getDateLine(layout.toSerializable(event)); @@ -213,7 +212,7 @@ public void testLayoutWithDatePatternUnix() { @Test public void testLayoutWithDatePatternUnixMillis() { - final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX_MILLIS").build(); + final HtmlLayout layout = HtmlLayout.newBuilder().setConfiguration(new DefaultConfiguration()).setDatePattern("UNIX_MILLIS").build(); final MyLogEvent event = new MyLogEvent(); final String actual = getDateLine(layout.toSerializable(event)); @@ -235,7 +234,7 @@ private String getDateLine(final String logEventString) { } private void testLayoutWithDatePatternFixedFormat(final FixedFormat format, final String timezone) { - final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern(format.name()).setTimezone(timezone).build(); + final HtmlLayout layout = HtmlLayout.newBuilder().setConfiguration(new DefaultConfiguration()).setDatePattern(format.name()).setTimezone(timezone).build(); final LogEvent event = new MyLogEvent(); final String actual = getDateLine(layout.toSerializable(event)); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java index 861a1f0e778..93017b3d597 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java @@ -17,19 +17,17 @@ package org.apache.logging.log4j.core.layout; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Locale; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.*; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationProcessor; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.test.BasicConfigurationFactory; import org.apache.logging.log4j.core.test.appender.ListAppender; @@ -37,6 +35,7 @@ import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.core.util.ProcessIdUtil; +import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.StructuredDataCollectionMessage; import org.apache.logging.log4j.message.StructuredDataMessage; import org.apache.logging.log4j.plugins.Node; @@ -88,7 +87,7 @@ public void testLayout() throws Exception { } // set up appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -164,7 +163,7 @@ public void testCollection() throws Exception { } // set up appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -235,7 +234,7 @@ public void testEscape() throws Exception { } // set up layout/appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, "#012", "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + null, null, true, "#012", "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -297,7 +296,7 @@ public void testException() throws Exception { } // set up layout/appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", "%xEx", true, null, null); + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", "%xEx", true, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -329,11 +328,7 @@ public void testException() throws Exception { * Test case for MDC logger field inclusion. */ @Test - public void testMDCLoggerFields() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - + public void testMDCLoggerFields() { final LoggerFields[] loggerFields = new LoggerFields[] { LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, null, null, false), LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source2", "%C.%M")}, null, null, false) @@ -341,42 +336,24 @@ public void testMDCLoggerFields() throws Exception { // set up layout/appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, loggerFields, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - // output starting message - root.info("starting logger fields test"); - - try { - - final List list = appender.getMessages(); - assertTrue(list.size() > 0, "Not enough list entries"); - assertTrue(list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields"), "No class/method"); - - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, loggerFields, new DefaultConfiguration()); + final LogEvent event = Log4jLogEvent + .newBuilder() + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("starting logger fields test")) + .setSource(new RuntimeException().getStackTrace()[0]) + .build(); + final String serializedEvent = layout.toSerializable(event); + assertTrue(serializedEvent.contains("Rfc5424LayoutTest.testMDCLoggerFields"), "No class/method"); } @Test public void testLoggerFields() { - final String[] fields = new String[] { + final String[] expectedToContain = new String[] { "[BAZ@32473 baz=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", "[RequestContext@3692 bar=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", "[SD-ID@32473 source=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]" }; - final List expectedToContain = Arrays.asList(fields); - - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } final LoggerFields[] loggerFields = new LoggerFields[] { LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, "SD-ID", @@ -386,30 +363,20 @@ public void testLoggerFields() { LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("bar", "%C.%M")}, null, null, false) }; - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - root.info("starting logger fields test"); - - try { - - final List list = appender.getMessages(); - assertTrue(list.size() > 0, "Not enough list entries"); - final String message = list.get(0); - assertTrue(message.contains("Rfc5424LayoutTest.testLoggerFields"), "No class/method"); - for (final String value : expectedToContain) { - assertTrue(message.contains(value), "Message expected to contain " + value + " but did not"); - } - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); + final StringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, new DefaultConfiguration()); + final LogEvent event = Log4jLogEvent + .newBuilder() + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("starting logger fields test")) + .setSource(new RuntimeException().getStackTrace()[0]) + .build(); + final String serializedEvent = layout.toSerializable(event); + assertTrue(serializedEvent.contains("Rfc5424LayoutTest.testLoggerFields"), "No class/method"); + for (final String value : expectedToContain) { + assertTrue(serializedEvent.contains(value), "Message expected to contain " + value + " but did not"); } + } @Test @@ -429,7 +396,7 @@ public void testDiscardEmptyLoggerFields() { }; final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, mdcId, - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -464,7 +431,7 @@ public void testSubstituteStructuredData() { } final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, false, mdcId, - null, null, true, null, "ATM", "MSG-ID", "key1, key2, locale", null, null, null, false, null, null); + null, null, true, null, "ATM", "MSG-ID", "key1, key2, locale", null, null, null, false, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -492,7 +459,7 @@ public void testParameterizedMessage() { } // set up appender final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, null, null); + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, null, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -520,6 +487,7 @@ void testLayoutBuilder() { } final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setConfig(new DefaultConfiguration()) .setFacility(Facility.LOCAL0) .setId("Event") .setEin("1234.56.7") @@ -551,7 +519,8 @@ void testLayoutBuilder() { @Test public void testLayoutBuilderDefaultValues() { - final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().build(); + final Configuration configuration = ctx.getConfiguration(); + final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().setConfig(configuration).build(); checkDefaultValues(layout); final PluginNamespace corePlugins = ctx.getInstanceFactory().getInstance(Core.PLUGIN_NAMESPACE_KEY); @@ -561,7 +530,7 @@ public void testLayoutBuilderDefaultValues() { node.getAttributes().put("name", "Rfc5242Layout"); final ConfigurableInstanceFactory factory = DI.createInitializedFactory(); - factory.registerBinding(Binding.from(Configuration.KEY).toInstance(ctx.getConfiguration())); + factory.registerBinding(Binding.from(Configuration.KEY).toInstance(configuration)); final ConfigurationProcessor processor = new ConfigurationProcessor(factory); final Rfc5424Layout object = processor.processNodeTree(node); assertNotNull(object); @@ -581,6 +550,7 @@ private void checkDefaultValues(final Rfc5424Layout layout) { @ValueSource(strings = { "123456789", "0", "2147483647", "123.45.6.78.9", "0.0.0.0.0.0.0.0.0.0.0.0.0.0" }) void testLayoutBuilderValidEids(final String eid) { final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setConfig(new DefaultConfiguration()) .setEin(eid) .build(); @@ -591,6 +561,7 @@ void testLayoutBuilderValidEids(final String eid) { @ValueSource(strings = { "abc", "someEid", "-1" }) void testLayoutBuilderInvalidEids(final String eid) { final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setConfig(new DefaultConfiguration()) .setEin(eid) .build(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java index 4c235a41efd..1147f0a6b5c 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java @@ -63,11 +63,11 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } @Override public void writeBytes(final byte[] data, final int offset, final int length) { - unsynchronizedWrite(data, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(data, offset, length, this); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java index ef2da1c9b76..a319b7b2d15 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java @@ -25,6 +25,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.test.BasicConfigurationFactory; import org.apache.logging.log4j.core.test.appender.ListAppender; @@ -59,6 +60,7 @@ public void testLayout() throws Exception { // set up appender // @formatter:off final SyslogLayout layout = SyslogLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setFacility(Facility.LOCAL0) .setIncludeNewLine(true) .build(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java new file mode 100644 index 00000000000..12e591aba04 --- /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 23e6f2378a6..3cfe2297ed6 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/appender/OutputStreamManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java index 1bdc922f709..47934be03b1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java @@ -18,14 +18,15 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; import org.apache.logging.log4j.core.util.Constants; /** @@ -177,26 +178,6 @@ public void writeBytes(final byte[] data, final int offset, final int length) { write(data, offset, length, false); } - @Override - public void withLock(final Runnable action) { - writeLock.lock(); - try { - action.run(); - } finally { - writeLock.unlock(); - } - } - - @Override - public T withLock(final Supplier supplier) { - writeLock.lock(); - try { - return supplier.get(); - } finally { - writeLock.unlock(); - } - } - /** * Some output streams synchronize writes while others do not. Synchronizing here insures that * log events won't be intertwined. @@ -291,9 +272,10 @@ protected void flushDestination() { * @since 2.6 */ protected void flushBuffer(final ByteBuffer buf) { + ((Buffer) buf).flip(); writeLock.lock(); try { - if (buf.flip().hasRemaining()) { + if (buf.hasRemaining()) { writeToDestination(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); } } finally { @@ -376,7 +358,7 @@ public void writeBytes(final ByteBuffer data) { } writeLock.lock(); try { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } finally { writeLock.unlock(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java index 3569099cbbf..64c737bfc14 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.layout.LoggerFields; import org.apache.logging.log4j.core.layout.Rfc5424Layout; import org.apache.logging.log4j.core.layout.SyslogLayout; @@ -113,6 +114,7 @@ public SyslogAppender build() { if (layout == null) { layout = RFC5424.equalsIgnoreCase(format) ? new Rfc5424Layout.Rfc5424LayoutBuilder() + .setConfig(new DefaultConfiguration()) .setFacility(facility) .setId(id) .setEin(enterpriseNumber) @@ -134,6 +136,7 @@ public SyslogAppender build() { .build() : // @formatter:off SyslogLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setFacility(facility) .setIncludeNewLine(newLine) .setEscapeNL(escapeNL) 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 08eceedaa86..c45f438fb00 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 @@ -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/AsyncLoggerConfigDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java index 636330cf539..fabd4642330 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 @@ -34,6 +34,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.Log4jPropertyKey; import org.apache.logging.log4j.core.impl.LogEventFactory; @@ -81,8 +82,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 75ca65affd3..2a0e473cf9b 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 269d47cbb74..9e511b79954 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 @@ -32,7 +32,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 +41,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 22e51625cbb..5cd5c551fe1 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 @@ -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 32f54913293..40c73929e6d 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 @@ -30,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 eb22159d52a..df1165e7acb 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 @@ -23,6 +23,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; @@ -38,7 +39,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; @@ -49,7 +49,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(); @@ -82,7 +82,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; @@ -90,7 +90,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, @@ -129,7 +129,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()); @@ -198,16 +199,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) { @@ -216,6 +232,11 @@ public Level getLevel() { return level; } + @Override + public void setLevel(final Level level) { + this.level = level; + } + @Override public Message getMessage() { if (message == null) { @@ -332,6 +353,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 @@ -344,11 +370,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; } @@ -357,44 +384,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; @@ -437,6 +505,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) { // If the data is not frozen, make a copy of it. // TODO: merge with MementoLogEvent#memento 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 a849c57e37a..2fda0b6b7a1 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 @@ -30,6 +30,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; @@ -39,6 +40,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 org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertyEnvironment; /** @@ -247,4 +249,13 @@ 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 a86f0470fee..13424c6cc35 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.message.Message; import org.apache.logging.log4j.plugins.Configurable; @@ -252,7 +251,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; } @@ -477,7 +477,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); } } @@ -506,7 +506,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); } } 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 8420c1f3be6..acd2f6fb2f6 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 @@ -43,9 +43,7 @@ import org.apache.logging.log4j.plugins.SingletonFactory; import org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; -import org.apache.logging.log4j.spi.CopyOnWrite; -import org.apache.logging.log4j.spi.DefaultThreadContextMap; -import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.spi.*; import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; @@ -64,6 +62,11 @@ */ public class DefaultBundle { + @SingletonFactory + public RecyclerFactory defaultRecyclerFactory() { + return LoggingSystem.getRecyclerFactory(); + } + @SingletonFactory @ConditionalOnMissingBinding public ContextSelector defaultContextSelector(final ConfigurableInstanceFactory instanceFactory) { @@ -101,8 +104,8 @@ public ContextDataInjector defaultContextDataInjector() { @SingletonFactory @ConditionalOnMissingBinding 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 777a9dfdfc0..40bdb57849d 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,7 +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.async.RingBufferLogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.ClockFactory; @@ -91,12 +91,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 28d7baf5734..7913e71f34e 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 @@ -45,4 +45,6 @@ default LogEvent createEvent( Throwable t) { return createEvent(loggerName, marker, fqcn, level, data, properties, t); } + + default void recycle(final LogEvent event) {} } 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 039de1232b0..73fa72eb90c 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,18 @@ 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.spi.Recycler; 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 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 +70,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 +87,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 +120,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 +165,7 @@ public String getLoggerFqcn() { return loggerFqcn; } + @Override public void setLoggerFqcn(final String loggerFqcn) { this.loggerFqcn = loggerFqcn; } @@ -175,6 +175,7 @@ public Marker getMarker() { return marker; } + @Override public void setMarker(final Marker marker) { this.marker = marker; } @@ -187,6 +188,7 @@ public Level getLevel() { return level; } + @Override public void setLevel(final Level level) { this.level = level; } @@ -196,6 +198,7 @@ public String getLoggerName() { return loggerName; } + @Override public void setLoggerName(final String loggerName) { this.loggerName = loggerName; } @@ -208,6 +211,7 @@ public Message getMessage() { return message; } + @Override public void setMessage(final Message msg) { if (msg instanceof ReusableMessage) { final ReusableMessage reusable = (ReusableMessage) msg; @@ -312,6 +316,7 @@ public Throwable getThrown() { return thrown; } + @Override public void setThrown(final Throwable thrown) { this.thrown = thrown; } @@ -330,6 +335,7 @@ public long getTimeMillis() { return instant.getEpochMillisecond(); } + @Override public void setTimeMillis(final long timeMillis) { this.instant.initFromEpochMilli(timeMillis, 0); } @@ -339,6 +345,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 +362,7 @@ public ThrowableProxy getThrownProxy() { return thrownProxy; } + @Override public void setSource(final StackTraceElement source) { this.source = source; } @@ -373,10 +385,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 +399,7 @@ public ThreadContext.ContextStack getContextStack() { return contextStack; } + @Override public void setContextStack(final ThreadContext.ContextStack contextStack) { this.contextStack = contextStack; } @@ -395,6 +409,7 @@ public long getThreadId() { return threadId; } + @Override public void setThreadId(final long threadId) { this.threadId = threadId; } @@ -404,6 +419,7 @@ public String getThreadName() { return threadName; } + @Override public void setThreadName(final String threadName) { this.threadName = threadName; } @@ -413,6 +429,7 @@ public int getThreadPriority() { return threadPriority; } + @Override public void setThreadPriority(final int threadPriority) { this.threadPriority = threadPriority; } @@ -442,14 +459,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 d47e1c765b5..834c0d9c847 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 @@ -23,32 +23,43 @@ 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 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 {@link 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) { - final 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() { - final MutableLogEvent result = mutableLogEventThreadLocal.get(); - return result == null || result.reserved ? createInstance(result) : result; - } - - private static MutableLogEvent createInstance(final MutableLogEvent existing) { - final 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(final 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 6871137bc7f..ee7f2ed920b 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 @@ -16,14 +16,17 @@ */ package org.apache.logging.log4j.core.layout; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.plugins.Inject; -import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Cast; @@ -32,6 +35,8 @@ */ public abstract class AbstractLayout implements StringLayout { + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + /** * Subclasses can extend this abstract Builder. * @@ -39,8 +44,16 @@ public abstract class AbstractLayout implements StringLayout { */ public abstract static class Builder> { + @PluginConfiguration private Configuration configuration; + + @PluginBuilderAttribute + private Charset charset; + + @PluginBuilderAttribute private byte[] footer; + + @PluginBuilderAttribute private byte[] header; public B asBuilder() { @@ -51,6 +64,10 @@ public Configuration getConfiguration() { return configuration; } + public Charset getCharset() { + return charset; + } + public byte[] getFooter() { return footer; } @@ -59,18 +76,22 @@ public byte[] getHeader() { return header; } - @Inject public B setConfiguration(final Configuration configuration) { this.configuration = configuration; return asBuilder(); } - public B setFooter(@PluginAttribute final byte[] footer) { + public B setCharset(final Charset charset) { + this.charset = charset; + return asBuilder(); + } + + public B setFooter(final byte[] footer) { this.footer = footer; return asBuilder(); } - public B setHeader(@PluginAttribute final byte[] header) { + public B setHeader(final byte[] header) { this.header = header; return asBuilder(); } @@ -87,6 +108,11 @@ public B setHeader(@PluginAttribute final byte[] header) { */ protected final Configuration configuration; + /** + * The character set used for encoding log events. + */ + protected final Charset charset; + /** * The number of events successfully processed by this layout. */ @@ -105,16 +131,19 @@ public B setHeader(@PluginAttribute final byte[] header) { /** * Constructs a layout with an optional header and footer. * - * @param configuration - * The configuration - * @param header - * The header to include when the stream is opened. May be null. - * @param footer - * The footer to add when the stream is closed. May be null. + * @param configuration a configuration + * @param charset a character set used for encoding log events; if null, UTF-8 will be used + * @param header a header to include when the stream is opened, may be null + * @param footer the footer to add when the stream is closed, may be null */ - public AbstractLayout(final Configuration configuration, final byte[] header, final byte[] footer) { + public AbstractLayout( + final Configuration configuration, + final Charset charset, + final byte[] header, + final byte[] footer) { super(); - this.configuration = configuration; + this.configuration = Objects.requireNonNull(configuration, "configuration"); + this.charset = charset != null ? charset : DEFAULT_CHARSET; this.header = header; this.footer = footer; } @@ -123,6 +152,11 @@ public Configuration getConfiguration() { return configuration; } + @Override + public Charset getCharset() { + return charset; + } + @Override public Map getContentFormat() { return Map.of(); @@ -162,30 +196,39 @@ 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)
+     * 
{@code
+     * @Category(Node.CATEGORY)
      * @Plugin(value = "MyLayout", elementType = Layout.ELEMENT_TYPE, printObject = true)
      * 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 = stringBuilderRecycler.acquire();
+     *         try {
+     *             convertLogEventToText(event, text);
+     *             StringBuilderEncoder encoder = stringBuilderEncoderRecycler.acquire();
+     *             try {
+     *                 encoder.encode(text, destination);
+     *             } finally {
+     *                 stringBuilderEncoderRecycler.release(encoder);
+     *             }
+     *         } finally {
+     *             stringBuilderRecycler.release(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#getStringBuilderEncoder() */ @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { final byte[] data = toByteArray(event); destination.writeBytes(data, 0, data.length); } + } 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 5c1587beb90..38ab7580c95 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 @@ -17,19 +17,17 @@ package org.apache.logging.log4j.core.layout; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.core.LogEvent; +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.Log4jPropertyKey; import org.apache.logging.log4j.core.impl.LogEventFactory; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.plugins.PluginAttribute; import org.apache.logging.log4j.plugins.PluginElement; -import org.apache.logging.log4j.spi.AbstractLogger; +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.PropertyKey; import org.apache.logging.log4j.util.StringBuilders; /** @@ -39,17 +37,15 @@ * performance: all characters are simply cast to bytes. *

*/ -public abstract class AbstractStringLayout extends AbstractLayout { +public abstract class AbstractStringLayout extends AbstractLayout implements StringLayout { public abstract static class Builder> extends AbstractLayout.Builder { - private Charset charset; + @PluginElement("footerSerializer") private Serializer footerSerializer; - private Serializer headerSerializer; - public Charset getCharset() { - return charset; - } + @PluginElement("headerSerializer") + private Serializer headerSerializer; public Serializer getFooterSerializer() { return footerSerializer; @@ -59,17 +55,12 @@ public Serializer getHeaderSerializer() { return headerSerializer; } - public B setCharset(@PluginAttribute final Charset charset) { - this.charset = charset; - return asBuilder(); - } - - public B setFooterSerializer(@PluginElement final Serializer footerSerializer) { + public B setFooterSerializer(final Serializer footerSerializer) { this.footerSerializer = footerSerializer; return asBuilder(); } - public B setHeaderSerializer(@PluginElement final Serializer headerSerializer) { + public B setHeaderSerializer(final Serializer headerSerializer) { this.headerSerializer = headerSerializer; return asBuilder(); } @@ -77,6 +68,7 @@ public B setHeaderSerializer(@PluginElement final Serializer headerSerializer) { } public interface Serializer extends Serializer2 { + String toSerializable(final LogEvent event); default boolean requiresLocation() { @@ -88,6 +80,7 @@ default StringBuilder toSerializable(final LogEvent event, final StringBuilder b builder.append(toSerializable(event)); return builder; } + } /** @@ -104,83 +97,78 @@ public interface Serializer2 { */ protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; - protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, - size(Log4jPropertyKey.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 PropertyKey property, final int defaultValue) { - return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); + protected static final int MAX_STRING_BUILDER_SIZE = Math.max( + DEFAULT_STRING_BUILDER_SIZE, + PropertiesUtil + .getProperties() + .getIntegerProperty( + Log4jPropertyKey.GC_LAYOUT_STRING_BUILDER_MAX_SIZE, + Math.multiplyExact(2, DEFAULT_STRING_BUILDER_SIZE))); + + protected static Recycler createStringBuilderRecycler(final RecyclerFactory recyclerFactory) { + return recyclerFactory.create( + () -> new StringBuilder(DEFAULT_STRING_BUILDER_SIZE), + stringBuilder -> { + StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); + stringBuilder.setLength(0); + }); } - protected static void trimToMaxSize(final StringBuilder stringBuilder) { - StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); - } - - private Encoder textEncoder; - /** - * The charset for the formatted message. - */ - private final Charset charset; - private final Serializer footerSerializer; private final Serializer headerSerializer; - protected AbstractStringLayout(final Charset charset) { - this(charset, (byte[]) null, (byte[]) null); + protected final Recycler> stringBuilderEncoderRecycler; + + protected final Recycler stringBuilderRecycler; + + protected AbstractStringLayout(final Configuration configuration, final Charset charset) { + this(configuration, charset, null, (byte[]) null); } /** * Builds a new layout. - * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be - * converted from strings to bytes. + * @param configuration a configuration * @param header the header bytes * @param footer the footer bytes */ - protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) { - super(null, header, footer); + protected AbstractStringLayout( + final Configuration configuration, + final Charset charset, + final byte[] header, + final byte[] footer) { + super(configuration, charset, header, footer); this.headerSerializer = null; this.footerSerializer = null; - this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; - textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; + final RecyclerFactory recyclerFactory = configuration.getRecyclerFactory(); + this.stringBuilderEncoderRecycler = createStringBuilderEncoderRecycler(recyclerFactory, getCharset()); + this.stringBuilderRecycler = createStringBuilderRecycler(recyclerFactory); } /** * Builds a new layout. - * @param config the configuration - * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be + * @param configuration a configuration + * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be * converted from strings to bytes. * @param headerSerializer the header bytes serializer * @param footerSerializer the footer bytes serializer */ - protected AbstractStringLayout(final Configuration config, final Charset aCharset, - final Serializer headerSerializer, final Serializer footerSerializer) { - super(config, null, null); + protected AbstractStringLayout( + final Configuration configuration, + final Charset charset, + final Serializer headerSerializer, + final Serializer footerSerializer) { + super(configuration, charset, null, null); this.headerSerializer = headerSerializer; this.footerSerializer = footerSerializer; - this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; - textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; + this.stringBuilderEncoderRecycler = createStringBuilderEncoderRecycler(configuration.getRecyclerFactory(), getCharset()); + this.stringBuilderRecycler = createStringBuilderRecycler(configuration.getRecyclerFactory()); + } + + private static Recycler> createStringBuilderEncoderRecycler( + final RecyclerFactory recyclerFactory, + final Charset charset) { + return recyclerFactory.create(() -> new StringBuilderEncoder(charset)); } protected byte[] getBytes(final String s) { @@ -228,18 +216,6 @@ public Serializer getHeaderSerializer() { return headerSerializer; } - /** - * Returns a {@code Encoder} that this Layout implementation can use for encoding log events. - * - * @return a {@code Encoder} - */ - protected Encoder getStringBuilderEncoder() { - if (textEncoder == null) { - textEncoder = new StringBuilderEncoder(getCharset()); - } - return textEncoder; - } - protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { final String serializable = serializeToString(serializer); if (serializable == null) { @@ -260,8 +236,7 @@ protected String serializeToString(final Serializer serializer) { final LoggerConfig rootLogger = config.getRootLogger(); final LogEventFactory logEventFactory = config.getComponent(LogEventFactory.KEY); final String fqcn = getClass().getName(); - final LogEvent logEvent = logEventFactory.createEvent( - rootLogger.getName(), null, fqcn, rootLogger.getLevel(), null, null, null); + final LogEvent logEvent = logEventFactory.createEvent(rootLogger.getName(), null, fqcn, rootLogger.getLevel(), null, null, null); return serializer.toSerializable(logEvent); } @@ -278,4 +253,5 @@ public byte[] toByteArray(final LogEvent event) { @Override public abstract String toSerializable(LogEvent event); + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestination.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestination.java index b6eaba2579d..247b2b7bdaa 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestination.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestination.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j.core.layout; import java.nio.ByteBuffer; -import java.util.function.Supplier; import org.apache.logging.log4j.core.appender.OutputStreamManager; @@ -54,7 +53,6 @@ public interface ByteBufferDestination { * synchronize themselves inside this method, if needed. * * @since 2.9 (see LOG4J2-1874) - * @see #unsynchronizedWrite(ByteBuffer) */ void writeBytes(ByteBuffer data); @@ -71,72 +69,7 @@ public interface ByteBufferDestination { * the method to be public breaks source compatibility. * * @since 2.9 (see LOG4J2-1874) - * @see #unsynchronizedWrite(byte[], int, int) */ void writeBytes(byte[] data, int offset, int length); - /** - * Writes the given data to this ByteBufferDestination without any synchronization. This is useful for - * implementing {@link #writeBytes(ByteBuffer)}. - * - * @since 3.0.0 (see LOG4J2-1874) - */ - default void unsynchronizedWrite(final ByteBuffer data) { - var destination = getByteBuffer(); - while (data.remaining() > destination.remaining()) { - final int originalLimit = data.limit(); - final int potentialLimit = data.position() + destination.remaining(); - final int limit = Math.min(originalLimit, potentialLimit); - destination.put(data.limit(limit)); - data.limit(originalLimit); - destination = drain(destination); - } - destination.put(data); - // No drain in the end. - } - - /** - * Writes the given data to this ByteBufferDestination without any synchronization. This is useful for - * implementing {@link #writeBytes(byte[], int, int)}. - * - * @since 3.0.0 (see LOG4J2-1874) - */ - default void unsynchronizedWrite(final byte[] data, final int offset, final int length) { - var destination = getByteBuffer(); - int position = offset; - int remaining = length; - while (remaining > destination.remaining()) { - final int chunk = destination.remaining(); - destination.put(data, position, remaining); - position += chunk; - remaining -= chunk; - destination = drain(destination); - } - destination.put(data, position, remaining); - // No drain in the end. - } - - /** - * Runs the provided action synchronized with the lock for this destination. This should be used instead - * of synchronizing on this instance directly. - * - * @since 3.0.0 - */ - default void withLock(final Runnable action) { - synchronized (this) { - action.run(); - } - } - - /** - * Runs the provided action synchronized with the lock for this destination and returns its result. - * This should be used instead of synchronizing on this instance directly. - * - * @since 3.0.0 - */ - default T withLock(final Supplier supplier) { - synchronized (this) { - return supplier.get(); - } - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestinationHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestinationHelper.java new file mode 100644 index 00000000000..a7965158ede --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ByteBufferDestinationHelper.java @@ -0,0 +1,73 @@ +/* + * 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.layout; + +import java.nio.ByteBuffer; + +/** + * Helper class for ByteBufferDestination implementors. + * + * @since 2.9 (see LOG4J2-1874) + */ +public final class ByteBufferDestinationHelper { + + private ByteBufferDestinationHelper() { + } + + /** + * Writes the specified data to the specified destination. Doesn't synchronize on the destination object. The helper + * method for {@link ByteBufferDestination#writeBytes(ByteBuffer)} implementations. + * + * @param source the data to write + * @param destination the {@code ByteBufferDestination} to write to + */ + public static void writeToUnsynchronized(final ByteBuffer source, final ByteBufferDestination destination) { + ByteBuffer destBuff = destination.getByteBuffer(); + while (source.remaining() > destBuff.remaining()) { + final int originalLimit = source.limit(); + source.limit(Math.min(source.limit(), source.position() + destBuff.remaining())); + destBuff.put(source); + source.limit(originalLimit); + destBuff = destination.drain(destBuff); + } + destBuff.put(source); + // No drain in the end. + } + + /** + * Writes the specified data to the specified destination. Doesn't synchronize on the destination object. The helper + * method for {@link ByteBufferDestination#writeBytes(byte[], int, int)} implementations. + * + * @param data the data to write + * @param offset where to start in the specified data array + * @param length the number of bytes to write + * @param destination the {@code ByteBufferDestination} to write to + */ + public static void writeToUnsynchronized(final byte[] data, int offset, int length, + final ByteBufferDestination destination) { + ByteBuffer buffer = destination.getByteBuffer(); + while (length > buffer.remaining()) { + final int chunk = buffer.remaining(); + buffer.put(data, offset, chunk); + offset += chunk; + length -= chunk; + buffer = destination.drain(buffer); + } + buffer.put(data, offset, length); + // No drain in the end. + } +} 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 e64ab9d3a9e..21d05ee5121 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 @@ -20,13 +20,8 @@ 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.*; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; @@ -40,19 +35,14 @@ import org.apache.logging.log4j.core.layout.internal.ListChecker; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Severity; -import org.apache.logging.log4j.core.util.JsonUtils; -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.*; 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.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.plugins.*; +import org.apache.logging.log4j.spi.Recycler; 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; @@ -113,6 +103,7 @@ public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) th private final PatternLayout layout; private final FieldWriter mdcWriter; private final FieldWriter mapWriter; + private final Recycler stringBuilderWriterRecycler; public static class Builder> extends AbstractStringLayout.Builder implements org.apache.logging.log4j.plugins.util.Builder { @@ -437,6 +428,7 @@ public B setMapPrefix(final String prefix) { } return asBuilder(); } + } private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, @@ -445,7 +437,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue final boolean includeNewLineDelimiter, final boolean omitEmptyFields, final ListChecker mdcChecker, final ListChecker mapChecker, final PatternLayout patternLayout, final String mdcPrefix, final String mapPrefix) { - super(config, StandardCharsets.UTF_8, null, null); + super(config, StandardCharsets.UTF_8); this.host = host != null ? host : NetUtils.getLocalHostname(); this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0]; if (config == null) { @@ -469,6 +461,13 @@ 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; + stringBuilderWriterRecycler = config.getRecyclerFactory().create( + () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE), + writer -> { + final StringBuilder stringBuilder = writer.getBuilder(); + StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); + stringBuilder.setLength(0); + }); } @Override @@ -512,8 +511,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 = stringBuilderRecycler.acquire(); + final byte[] bytes; + try { + bytes = getBytes(toText(event, text, false).toString()); + } finally { + stringBuilderRecycler.release(text); + } return compressionType != CompressionType.OFF && bytes.length > compressionThreshold ? compress(bytes) : bytes; } @@ -523,9 +527,18 @@ 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 = stringBuilderRecycler.acquire(); + try { + final StringBuilder encodedEvent = toText(event, text, true); + final Encoder helper = stringBuilderEncoderRecycler.acquire(); + try { + helper.encode(encodedEvent, destination); + } finally { + stringBuilderEncoderRecycler.release(helper); + } + } finally { + stringBuilderRecycler.release(text); + } } @Override @@ -552,8 +565,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 = stringBuilderRecycler.acquire(); + try { + return toText(event, text, false).toString(); + } finally { + stringBuilderRecycler.release(text); + } } private StringBuilder toText(final LogEvent event, final StringBuilder builder, final boolean gcFree) { @@ -562,7 +579,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\":\""); @@ -599,12 +618,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 = stringBuilderRecycler.acquire(); + try { + layout.serialize(event, messageBuffer); + JsonUtils.quoteAsString(messageBuffer, builder); + } finally { + stringBuilderRecycler.release(messageBuffer); + } } else { if (includeStacktrace) { - JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder); + final StringBuilderWriter writer = stringBuilderWriterRecycler.acquire(); + try { + formatThrowableTo(writer, event.getThrown()); + JsonUtils.quoteAsString(writer.getBuilder(), builder); + } finally { + stringBuilderWriterRecycler.release(writer); + } } else { JsonUtils.quoteAsString(event.getThrown().toString(), builder); } @@ -617,12 +646,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 = stringBuilderRecycler.acquire(); try { ((StringBuilderFormattable) message).formatTo(messageBuffer); JsonUtils.quoteAsString(messageBuffer, builder); } finally { - trimToMaxSize(messageBuffer); + stringBuilderRecycler.release(messageBuffer); } } else { JsonUtils.quoteAsString(toNullSafeString(message.getFormattedMessage()), builder); @@ -656,7 +685,15 @@ public void accept(final String key, final Object value, final StringBuilder str final String stringValue = String.valueOf(value); if (checker.check(key) && (Strings.isNotEmpty(stringValue) || !omitEmptyFields)) { stringBuilder.append(QU); - JsonUtils.quoteAsString(Strings.concat(prefix, key), stringBuilder); + final StringBuilderWriter stringBuilderWriter = stringBuilderWriterRecycler.acquire(); + final StringBuilder tmpStringBuilder = stringBuilderWriter.getBuilder(); + try { + tmpStringBuilder.append(prefix); + tmpStringBuilder.append(key); + JsonUtils.quoteAsString(tmpStringBuilder, stringBuilder); + } finally { + stringBuilderWriterRecycler.release(stringBuilderWriter); + } stringBuilder.append("\":\""); JsonUtils.quoteAsString(toNullSafeString(stringValue), stringBuilder); stringBuilder.append(QC); @@ -668,18 +705,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; } @@ -691,22 +716,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; } /** @@ -725,10 +745,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 3b6eb31a7ec..d9cb9fb8fdf 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 @@ -32,7 +32,10 @@ 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.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.pattern.DatePatternConverter; import org.apache.logging.log4j.core.util.Transform; import org.apache.logging.log4j.plugins.Configurable; @@ -103,9 +106,18 @@ public FontSize larger() { } } - private HtmlLayout(final boolean locationInfo, final String title, final String contentType, final Charset charset, - final String font, final String fontSize, final String headerSize, final String datePattern, final String timezone) { - super(charset); + private HtmlLayout( + final Configuration configuration, + final boolean locationInfo, + final String title, + final String contentType, + final Charset charset, + final String font, + final String fontSize, + final String headerSize, + final String datePattern, + final String timezone) { + super(configuration, charset); this.locationInfo = locationInfo; this.title = title; this.contentType = addCharsetToContentType(contentType); @@ -150,91 +162,92 @@ private String addCharsetToContentType(final String contentType) { */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder sbuf = getStringBuilder(); + final StringBuilder sbuf = stringBuilderRecycler.acquire(); + 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); + } + + 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); + } - return sbuf.toString(); + final Throwable throwable = event.getThrown(); + if (throwable != null) { + sbuf.append(""); + appendThrowableAsHtml(throwable, sbuf); + sbuf.append("").append(Strings.LINE_SEPARATOR); + } + + return sbuf.toString(); + } finally { + stringBuilderRecycler.release(sbuf); + } } @Override - /** - * @return The content type. - */ public String getContentType() { return contentType; } @@ -294,41 +307,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 = stringBuilderRecycler.acquire(); + 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 { + stringBuilderRecycler.release(sbuf); } - appendLs(sbuf, ""); - appendLs(sbuf, ""); - return sbuf.toString().getBytes(getCharset()); } /** @@ -337,11 +354,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 = stringBuilderRecycler.acquire(); + try { + appendLs(sbuf, ""); + appendLs(sbuf, "
"); + appendLs(sbuf, ""); + return getBytes(sbuf.toString()); + } finally { + stringBuilderRecycler.release(sbuf); + } } /** @@ -350,7 +371,7 @@ public byte[] getFooter() { * @return an HTML Layout. */ public static HtmlLayout createDefaultLayout() { - return newBuilder().build(); + return newBuilder().setConfiguration(new DefaultConfiguration()).build(); } @PluginFactory @@ -360,6 +381,9 @@ public static Builder newBuilder() { public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { + @PluginConfiguration + private Configuration configuration; + @PluginBuilderAttribute private boolean locationInfo = false; @@ -387,6 +411,11 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui private Builder() { } + public Builder setConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + public Builder setLocationInfo(final boolean locationInfo) { this.locationInfo = locationInfo; return this; @@ -433,8 +462,9 @@ public HtmlLayout build() { if (contentType == null) { contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset; } - return new HtmlLayout(locationInfo, title, contentType, charset, fontName, fontSize.getFontSize(), - fontSize.larger().getFontSize(), datePattern, timezone); + return new HtmlLayout( + configuration, locationInfo, title, contentType, charset, fontName, fontSize.getFontSize(), + fontSize.larger().getFontSize(), datePattern, timezone); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LockingStringBuilderEncoder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LockingStringBuilderEncoder.java index 6bfb9bdafca..7d6e5064548 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LockingStringBuilderEncoder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LockingStringBuilderEncoder.java @@ -23,7 +23,6 @@ import java.util.Objects; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.status.StatusLogger; /** * Encoder for StringBuilders that locks on the ByteBufferDestination. @@ -53,17 +52,14 @@ private CharBuffer getCharBuffer() { public void encode(final StringBuilder source, final ByteBufferDestination destination) { try { // This synchronized is needed to be able to call destination.getByteBuffer() - destination.withLock(() -> TextEncoderHelper.encodeText( - charsetEncoder, cachedCharBuffer, destination.getByteBuffer(), source, destination)); - } catch (final Exception ex) { - logEncodeTextException(ex, source, destination); - TextEncoderHelper.encodeTextFallBack(charset, source, destination); + synchronized (destination) { + TextEncoderHelper.encodeText(charsetEncoder, cachedCharBuffer, destination.getByteBuffer(), source, + destination); + } + } catch (final Exception error) { + TextEncoderHelper.encodeTextFallback(charset, source, destination, error); } } - private void logEncodeTextException(final Exception ex, final StringBuilder text, - final ByteBufferDestination destination) { - StatusLogger.getLogger().error("Recovering from LockingStringBuilderEncoder.encode('{}') error", text, ex); - } } 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 8f97c5c0cb1..e0dfdad4fdb 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 @@ -37,6 +37,7 @@ 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.Recycler; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; import org.apache.logging.log4j.util.Strings; @@ -193,22 +194,19 @@ 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 = stringBuilderRecycler.acquire(); + StringBuilder text = builder; + try { + text = eventSerializer.toSerializable(event, builder); + final Encoder encoder = stringBuilderEncoderRecycler.acquire(); + try { + encoder.encode(text, destination); + } finally { + stringBuilderEncoderRecycler.release(encoder); + } + } finally { + stringBuilderRecycler.release(text); + } } /** @@ -239,21 +237,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); } } @@ -284,18 +284,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); } } @@ -320,19 +322,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); } } @@ -380,6 +385,7 @@ public Serializer build() { if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) { return null; } + final Recycler recycler = createStringBuilderRecycler(configuration.getRecyclerFactory()); if (patternSelector == null) { try { final PatternParser parser = createPatternParser(configuration); @@ -395,14 +401,14 @@ public Serializer build() { } } final 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) { @@ -451,20 +457,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 56aea3e7275..b3cf2756ad7 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 @@ -140,7 +140,7 @@ private Rfc5424Layout(final Configuration config, final Facility facility, final final String mdcPrefix, final String eventPrefix, final String appName, final String messageId, final String excludes, final String includes, final String required, final Charset charset, final String exceptionPattern, final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) { - super(charset); + super(config, charset); final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class); exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern); this.facility = facility; @@ -245,7 +245,7 @@ private static PatternParser createPatternParser(final Configuration config, } PatternParser parser = config.getComponent(COMPONENT_KEY); if (parser == null) { - parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class); + parser = new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class); config.addComponent(COMPONENT_KEY, parser); parser = config.getComponent(COMPONENT_KEY); } @@ -277,24 +277,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 = stringBuilderRecycler.acquire(); + 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 { + stringBuilderRecycler.release(buf); + } } private void appendPriority(final StringBuilder buffer, final Level logLevel) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java index 8c58a1e5153..759943708b7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java @@ -24,73 +24,60 @@ import java.util.Objects; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.spi.RecyclerFactory; /** - * Encoder for StringBuilders that uses ThreadLocals to avoid locking as much as possible. + * {@link Encoder} for {@link StringBuilder}s. + *

+ * {@link StringBuilderEncoder#encode(StringBuilder, ByteBufferDestination) encode()} is not thread-safe! + * Users are expected to recycle {@link StringBuilderEncoder} instances, e.g., using a {@link RecyclerFactory}. + *

*/ public class StringBuilderEncoder implements Encoder { - /** - * This ThreadLocal uses raw and inconvenient Object[] to store three heterogeneous objects (CharEncoder, CharBuffer - * and ByteBuffer) instead of a custom class, because it needs to contain JDK classes, no custom (Log4j) classes. - * Where possible putting only JDK classes in ThreadLocals is preferable to avoid memory leaks in web containers: - * the Log4j classes may be loaded by a separate class loader which cannot be garbage collected if a thread pool - * threadlocal still has a reference to it. - * - * Using just one ThreadLocal instead of three separate ones is an optimization: {@link ThreadLocal.ThreadLocalMap} - * is polluted less, {@link ThreadLocal.ThreadLocalMap#get()} is called only once on each call to {@link #encode} - * instead of three times. - */ - private final ThreadLocal threadLocal = new ThreadLocal<>(); + private final CharsetEncoder charsetEncoder; + + private final CharBuffer charBuffer; + + private final ByteBuffer byteBuffer; + private final Charset charset; - private final int charBufferSize; - private final int byteBufferSize; public StringBuilderEncoder(final Charset charset) { this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, Constants.ENCODER_BYTE_BUFFER_SIZE); } public StringBuilderEncoder(final Charset charset, final int charBufferSize, final int byteBufferSize) { - this.charBufferSize = charBufferSize; - this.byteBufferSize = byteBufferSize; this.charset = Objects.requireNonNull(charset, "charset"); + this.charsetEncoder = charset + .newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + this.charBuffer = CharBuffer.allocate(charBufferSize); + this.byteBuffer = ByteBuffer.allocate(byteBufferSize); } + /** + * Encodes the given source to the given destination. + *

+ * This method is not thread-safe! + * Users are expected to recycle {@link StringBuilderEncoder} instances, e.g., using a {@link RecyclerFactory}. + *

+ * + * @param source a source + * @param destination a destination + */ @Override public void encode(final StringBuilder source, final ByteBufferDestination destination) { try { - final Object[] threadLocalState = getThreadLocalState(); - final CharsetEncoder charsetEncoder = (CharsetEncoder) threadLocalState[0]; - final CharBuffer charBuffer = (CharBuffer) threadLocalState[1]; - final ByteBuffer byteBuffer = (ByteBuffer) threadLocalState[2]; TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination); - } catch (final Exception ex) { - logEncodeTextException(ex, source); - TextEncoderHelper.encodeTextFallBack(charset, source, destination); + } catch (final Exception error) { + TextEncoderHelper.encodeTextFallback(charset, source, destination, error); + } finally { + charsetEncoder.reset(); + charBuffer.clear(); + byteBuffer.clear(); } } - private Object[] getThreadLocalState() { - Object[] threadLocalState = threadLocal.get(); - if (threadLocalState == null) { - threadLocalState = new Object[] { - charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE), - CharBuffer.allocate(charBufferSize), - ByteBuffer.allocate(byteBufferSize) - }; - threadLocal.set(threadLocalState); - } else { - ((CharsetEncoder) threadLocalState[0]).reset(); - ((CharBuffer) threadLocalState[1]).clear(); - ((ByteBuffer) threadLocalState[2]).clear(); - } - return threadLocalState; - } - - private static void logEncodeTextException(final Exception ex, final StringBuilder text) { - StatusLogger.getLogger().error("Recovering from StringBuilderEncoder.encode('{}') error: {}", text, ex, ex); - } - } 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 b44f793a42d..ae736e88771 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 @@ -30,6 +30,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Priority; import org.apache.logging.log4j.core.util.NetUtils; @@ -76,7 +77,7 @@ public Builder() { @Override public SyslogLayout build() { - return new SyslogLayout(facility, includeNewLine, escapeNL, getCharset()); + return new SyslogLayout(getConfiguration(), facility, includeNewLine, escapeNL, getCharset()); } public Facility getFacility() { @@ -133,8 +134,13 @@ public static > B newBuilder() { */ private final String localHostname = NetUtils.getLocalHostname(); - protected SyslogLayout(final Facility facility, final boolean includeNL, final String escapeNL, final Charset charset) { - super(charset); + private SyslogLayout( + final Configuration configuration, + final Facility facility, + final boolean includeNL, + final String escapeNL, + final Charset charset) { + super(configuration, charset); this.facility = facility; this.includeNewLine = includeNL; this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL); @@ -148,26 +154,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); - } - buf.append(message); + final StringBuilder buf = stringBuilderRecycler.acquire(); + + 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'); + if (includeNewLine) { + buf.append('\n'); + } + return buf.toString(); + } finally { + stringBuilderRecycler.release(buf); } - return buf.toString(); } private void addDate(final long timestamp, final StringBuilder buf) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java index 3785e71fc27..71a7fa7a1c9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java @@ -23,6 +23,8 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; +import org.apache.logging.log4j.status.StatusLogger; + /** * Helper class to encode text to binary data without allocating temporary objects. * @@ -33,9 +35,15 @@ public class TextEncoderHelper { private TextEncoderHelper() { } - static void encodeTextFallBack(final Charset charset, final StringBuilder text, - final ByteBufferDestination destination) { - final byte[] bytes = text.toString().getBytes(charset); + /* for JIT-ergonomics: */ static void encodeTextFallback( + final Charset charset, + final StringBuilder source, + final ByteBufferDestination destination, + final Exception error) { + StatusLogger + .getLogger() + .error("`TextEncoderHelper.encodeText()` failure, falling back to `String#getBytes(Charset)`", error); + final byte[] bytes = source.toString().getBytes(charset); destination.writeBytes(bytes, 0, bytes.length); } @@ -79,7 +87,9 @@ private static void writeEncodedText(final CharsetEncoder charsetEncoder, final } result = charsetEncoder.flush(byteBuf); if (!result.isUnderflow()) { - destination.withLock(() -> flushRemainingBytes(charsetEncoder, destination, byteBuf)); + synchronized (destination) { + flushRemainingBytes(charsetEncoder, destination, byteBuf); + } return; } // Thread-safety note: no explicit synchronization on ByteBufferDestination below. This is safe, because @@ -103,9 +113,12 @@ private static void writeEncodedText(final CharsetEncoder charsetEncoder, final * @since 2.9 */ private static void writeChunkedEncodedText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, - final ByteBufferDestination destination, final ByteBuffer byteBuf, final CoderResult result) { - destination.withLock(() -> flushRemainingBytes(charsetEncoder, destination, - writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, true, destination, byteBuf, result))); + final ByteBufferDestination destination, ByteBuffer byteBuf, final CoderResult result) { + synchronized (destination) { + byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, true, destination, byteBuf, + result); + flushRemainingBytes(charsetEncoder, destination, byteBuf); + } } /** @@ -117,7 +130,7 @@ private static void writeChunkedEncodedText(final CharsetEncoder charsetEncoder, * @since 2.9 */ private static void encodeChunkedText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, - final ByteBuffer byteBuf, final StringBuilder text, final ByteBufferDestination destination) { + ByteBuffer byteBuf, final StringBuilder text, final ByteBufferDestination destination) { // LOG4J2-1874 ByteBuffer, CharBuffer and CharsetEncoder are thread-local, so no need to synchronize while // modifying these objects. Postpone synchronization until accessing the ByteBufferDestination. @@ -136,27 +149,24 @@ private static void encodeChunkedText(final CharsetEncoder charsetEncoder, final writeEncodedText(charsetEncoder, charBuf, byteBuf, destination, result); return; } - final CoderResult intermediateResult = result; - final int intermediatePosition = start; - destination.withLock(() -> { - var buf = writeAndEncodeAsMuchAsPossible( - charsetEncoder, charBuf, false, destination, byteBuf, intermediateResult); - boolean end = false; - int position = intermediatePosition; - while (!end) { - CoderResult coderResult = CoderResult.UNDERFLOW; - while (!end && coderResult.isUnderflow()) { + synchronized (destination) { + byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf, + result); + while (!endOfInput) { + result = CoderResult.UNDERFLOW; + while (!endOfInput && result.isUnderflow()) { charBuf.clear(); - final int copied = copy(text, position, charBuf); - position += copied; - end = position >= text.length(); + final int copied = copy(text, start, charBuf); + start += copied; + endOfInput = start >= text.length(); charBuf.flip(); - coderResult = charsetEncoder.encode(charBuf, buf, end); + result = charsetEncoder.encode(charBuf, byteBuf, endOfInput); } - buf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, end, destination, buf, coderResult); + byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf, + result); } - flushRemainingBytes(charsetEncoder, destination, buf); - }); + flushRemainingBytes(charsetEncoder, destination, byteBuf); + } } /** @@ -218,17 +228,17 @@ private static ByteBuffer drainIfByteBufferFull(final ByteBufferDestination dest if (result.isOverflow()) { // byte buffer full // all callers already synchronize on destination but for safety ensure we are synchronized because // below calls to drain() may cause destination to swap in a new ByteBuffer object - return destination.withLock(() -> { + synchronized (destination) { final ByteBuffer destinationBuffer = destination.getByteBuffer(); if (destinationBuffer != temp) { temp.flip(); - destination.unsynchronizedWrite(temp); + ByteBufferDestinationHelper.writeToUnsynchronized(temp, destination); temp.clear(); return destination.getByteBuffer(); } else { return destination.drain(destinationBuffer); } - }); + } } else { return temp; } @@ -247,7 +257,7 @@ private static void flushRemainingBytes(final CharsetEncoder charsetEncoder, } if (temp.remaining() > 0 && temp != destination.getByteBuffer()) { temp.flip(); - destination.unsynchronizedWrite(temp); + ByteBufferDestinationHelper.writeToUnsynchronized(temp, destination); temp.clear(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java index ca1f9fb6c59..b26b89720d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java @@ -314,13 +314,16 @@ private void connect(final InetSocketAddress socketAddress) throws IOException { final OutputStream newOS = sock.getOutputStream(); final InetAddress prev = socket != null ? socket.getInetAddress() : null; final OutputStream oldOS = getOutputStream(); - owner.withLock(() -> { + writeLock.lock(); + try { Closer.closeSilently(oldOS); setOutputStream(newOS); socket = sock; reconnector = null; shutdown = true; - }); + } finally { + writeLock.unlock(); + } final String type = prev != null && prev.getHostAddress().equals(socketAddress.getAddress().getHostAddress()) ? "reestablished" : "established"; LOGGER.debug("Connection to {}:{} {}: {}", host, port, type, socket); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java index 56cc1bea959..972be86cfb2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java @@ -21,7 +21,6 @@ import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.Strings; /** * Equals pattern converter. @@ -58,7 +57,7 @@ public static RepeatPatternConverter newInstance(final Configuration config, fin String result = options[0]; try { count = Integer.parseInt(options[1].trim()); - result = Strings.repeat(options[0], count); + result = options[0].repeat(count); } catch (final Exception ex) { LOGGER.error("The repeat count is not an integer: {}", options[1].trim()); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java index 9e39a941579..d3353ccbc2f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core.util; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.spi.Recycler; import org.apache.logging.log4j.util.Lazy; /** @@ -55,52 +57,49 @@ public final class JsonUtils { /** * Temporary buffer used for composing quote/escape sequences */ - private final static ThreadLocal _qbufLocal = new ThreadLocal<>(); - - private static char[] getQBuf() { - char[] _qbuf = _qbufLocal.get(); - if (_qbuf == null) { - _qbuf = new char[6]; - _qbuf[0] = '\\'; - _qbuf[2] = '0'; - _qbuf[3] = '0'; - - _qbufLocal.set(_qbuf); - } - return _qbuf; - } + private final static Recycler qbufRecycler = LoggingSystem.getRecyclerFactory().create(() -> { + char[] qbuf = new char[6]; + qbuf[0] = '\\'; + qbuf[2] = '0'; + qbuf[3] = '0'; + return qbuf; + }); /** * Quote text contents using JSON standard quoting, and append results to a supplied {@link StringBuilder}. */ public static void quoteAsString(final CharSequence input, final StringBuilder output) { - final char[] qbuf = getQBuf(); - final int[] escCodes = ESC_CODES.value(); - final int escCodeCount = escCodes.length; - int inPtr = 0; - final int inputLen = input.length(); + final char[] qbuf = qbufRecycler.acquire(); + try { + final int[] escCodes = ESC_CODES.value(); + final int escCodeCount = escCodes.length; + int inPtr = 0; + final int inputLen = input.length(); - outer: - while (inPtr < inputLen) { - tight_loop: - while (true) { - final char c = input.charAt(inPtr); - if (c < escCodeCount && escCodes[c] != 0) { - break tight_loop; - } - output.append(c); - if (++inPtr >= inputLen) { - break outer; + outer: + while (inPtr < inputLen) { + tight_loop: + while (true) { + final char c = input.charAt(inPtr); + if (c < escCodeCount && escCodes[c] != 0) { + break tight_loop; + } + output.append(c); + if (++inPtr >= inputLen) { + break outer; + } } - } - // something to escape; 2 or 6-char variant? - final char d = input.charAt(inPtr++); - final int escCode = escCodes[d]; - final int length = (escCode < 0) - ? _appendNumeric(d, qbuf) - : _appendNamed(escCode, qbuf); + // something to escape; 2 or 6-char variant? + final char d = input.charAt(inPtr++); + final int escCode = escCodes[d]; + final int length = (escCode < 0) + ? _appendNumeric(d, qbuf) + : _appendNamed(escCode, qbuf); - output.append(qbuf, 0, length); + output.append(qbuf, 0, length); + } + } finally { + qbufRecycler.release(qbuf); } } 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 0402931198a..cffc94f9a69 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 @@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; @@ -43,11 +44,11 @@ public class CsvLogEventLayout extends AbstractCsvLayout { public static CsvLogEventLayout createDefaultLayout() { - return new CsvLogEventLayout(null, Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); + return new CsvLogEventLayout(new DefaultConfiguration(), Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); } public static CsvLogEventLayout createLayout(final CSVFormat format) { - return new CsvLogEventLayout(null, Charset.forName(DEFAULT_CHARSET), format, null, null); + return new CsvLogEventLayout(new DefaultConfiguration(), Charset.forName(DEFAULT_CHARSET), format, null, null); } @PluginFactory @@ -77,7 +78,7 @@ protected CsvLogEventLayout(final Configuration config, final Charset charset, f @Override public String toSerializable(final LogEvent event) { - final StringBuilder buffer = getStringBuilder(); + final StringBuilder buffer = stringBuilderRecycler.acquire(); final CSVFormat format = getFormat(); try { format.print(event.getNanoTime(), buffer, true); @@ -99,6 +100,8 @@ public String toSerializable(final LogEvent event) { } catch (final IOException e) { StatusLogger.getLogger().error(event.toString(), e); return format.getCommentMarker() + " " + e; + } finally { + stringBuilderRecycler.release(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 0bdc7aee00e..bee6e547fb5 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 @@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.plugins.Configurable; @@ -52,11 +53,11 @@ public class CsvParameterLayout extends AbstractCsvLayout { public static AbstractCsvLayout createDefaultLayout() { - return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); + return new CsvParameterLayout(new DefaultConfiguration(), Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); } public static AbstractCsvLayout createLayout(final CSVFormat format) { - return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), format, null, null); + return new CsvParameterLayout(new DefaultConfiguration(), Charset.forName(DEFAULT_CHARSET), format, null, null); } @PluginFactory @@ -88,13 +89,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 = stringBuilderRecycler.acquire(); try { getFormat().printRecord(buffer, parameters); return buffer.toString(); } catch (final IOException e) { StatusLogger.getLogger().error(message, e); return getFormat().getCommentMarker() + " " + e; + } finally { + stringBuilderRecycler.release(buffer); } } diff --git a/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayoutTest.java b/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayoutTest.java index b5c530f10c3..94d650d017e 100644 --- a/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayoutTest.java +++ b/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayoutTest.java @@ -48,7 +48,7 @@ public class CsvLogEventLayoutTest { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(null, "Excel", null, null, null, null, null, + final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(ctx.getConfiguration(), "Excel", null, null, null, null, null, null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } diff --git a/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvParameterLayoutTest.java b/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvParameterLayoutTest.java index 415eebe5730..a29eb8c2727 100644 --- a/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvParameterLayoutTest.java +++ b/log4j-csv/src/test/java/org/apache/logging/log4j/csv/layout/CsvParameterLayoutTest.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.test.junit.LoggerContextRule; @@ -69,7 +70,7 @@ public CsvParameterLayoutTest(final LoggerContextRule contextRule) { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvParameterLayout.createLayout(null, "Excel", null, null, null, null, null, + final AbstractCsvLayout layout = CsvParameterLayout.createLayout(new DefaultConfiguration(), "Excel", null, null, null, null, null, null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java index 05d30c9fe82..63a1bff6cd9 100644 --- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java +++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.Rfc5424Layout; import org.apache.logging.log4j.core.net.Facility; @@ -255,6 +256,7 @@ public static FlumeAppender createAppender( if (layout == null) { final int enterpriseNumber = Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER; layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setConfig(new DefaultConfiguration()) .setFacility(Facility.LOCAL0) .setEin(String.valueOf(enterpriseNumber)) .setIncludeMDC(true) diff --git a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java index 636e55629ab..d653c9e6f63 100644 --- a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java +++ b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.test.AvailablePortFinder; import org.apache.logging.log4j.core.test.categories.Appenders; import org.apache.logging.log4j.smtp.MimeMessageBuilder; @@ -115,6 +116,7 @@ public void testDelivery() { ThreadContext.put(subjectKey, subjectValue); final int smtpPort = AvailablePortFinder.getNextAvailable(); final SmtpAppender appender = SmtpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setName("Test") .setTo("to@example.com") .setCc("cc@example.com") diff --git a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java index 9779480b171..65ceccc1f33 100644 --- a/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java +++ b/log4j-jakarta-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.RingBufferLogEvent; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.MementoMessage; import org.apache.logging.log4j.core.impl.MutableLogEvent; @@ -45,7 +46,7 @@ void testCreateManagerName() { } private void testAdd(final LogEvent event) { - final SmtpManager smtpManager = SmtpManager.getSmtpManager(null, "to", "cc", "bcc", "from", "replyTo", "subject", "protocol", "host", 0, "username", "password", false, "filterName", 10, null); + final SmtpManager smtpManager = SmtpManager.getSmtpManager(new DefaultConfiguration(), "to", "cc", "bcc", "from", "replyTo", "subject", "protocol", "host", 0, "username", "password", false, "filterName", 10, null); smtpManager.removeAllBufferedEvents(); // in case this smtpManager is reused smtpManager.add(event); 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 af252875fc8..06b4c230580 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 @@ -43,11 +43,7 @@ import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.jackson.AbstractJacksonLayout; import org.apache.logging.log4j.jackson.json.Log4jJsonObjectMapper; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.message.ReusableMessageFactory; -import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.*; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; import org.apache.logging.log4j.util.SortedArrayStringMap; @@ -142,6 +138,7 @@ private String prepareJsonForObjectMessageAsJsonObjectTests(final int value, fin .setTimeMillis(1).build(); // @formatter:off final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setCompact(true) .setObjectMessageAsJsonObject(objectMessageAsJsonObject) .build(); @@ -153,6 +150,7 @@ private String prepareJsonForStacktraceTests(final boolean stacktraceAsString) { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); // @formatter:off final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setCompact(true) .setIncludeStacktrace(true) .setStacktraceAsString(stacktraceAsString) @@ -162,8 +160,9 @@ private String prepareJsonForStacktraceTests(final boolean stacktraceAsString) { } @Test - public void testAdditionalFields() throws Exception { + public void testAdditionalFields() { final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setComplete(false) @@ -182,8 +181,9 @@ public void testAdditionalFields() throws Exception { } @Test - public void testMutableLogEvent() throws Exception { + public void testMutableLogEvent() { final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setComplete(false) @@ -210,6 +210,7 @@ private void testAllFeatures(final boolean locationInfo, final boolean compact, final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); // @formatter:off final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(locationInfo) .setProperties(includeContext) .setPropertiesAsList(contextMapAslist) @@ -353,8 +354,9 @@ public void testLocationOnCustomEndOfLine() throws Exception { } @Test - public void testIncludeNullDelimiterFalse() throws Exception { + public void testIncludeNullDelimiterFalse() { final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setCompact(true) .setIncludeNullDelimiter(false) .build(); @@ -363,8 +365,9 @@ public void testIncludeNullDelimiterFalse() throws Exception { } @Test - public void testIncludeNullDelimiterTrue() throws Exception { + public void testIncludeNullDelimiterTrue() { final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setCompact(true) .setIncludeNullDelimiter(true) .build(); @@ -376,7 +379,7 @@ public void testIncludeNullDelimiterTrue() throws Exception { * Test case for MDC conversion pattern. */ @Test - public void testLayout() throws Exception { + public void testLayout() { final Map appenders = this.rootLogger.getAppenders(); for (final Appender appender : appenders.values()) { this.rootLogger.removeAppender(appender); @@ -440,6 +443,7 @@ public void testLayoutLoggerName() throws Exception { final boolean propertiesAsList = false; // @formatter:off final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setPropertiesAsList(propertiesAsList) @@ -470,6 +474,7 @@ public void testLayoutLoggerName() throws Exception { public void testLayoutMessageWithCurlyBraces() throws Exception { final boolean propertiesAsList = false; final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setPropertiesAsList(propertiesAsList) @@ -498,6 +503,7 @@ public void testLayoutMessageWithCurlyBraces() throws Exception { public void testReusableLayoutMessageWithCurlyBraces() throws Exception { final boolean propertiesAsList = false; final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setPropertiesAsList(propertiesAsList) @@ -507,7 +513,8 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception { .setCharset(StandardCharsets.UTF_8) .setIncludeStacktrace(true) .build(); - final Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE; + final Message message = factory.newMessage("Testing {}", new TestObj()); try { final Log4jLogEvent expected = Log4jLogEvent.newBuilder() .setLoggerName("a.B") @@ -524,7 +531,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); } } @@ -533,6 +540,7 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception { public void testLayoutRingBufferEventReusableMessageWithCurlyBraces() throws Exception { final boolean propertiesAsList = false; final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(false) .setProperties(false) .setPropertiesAsList(propertiesAsList) @@ -542,7 +550,8 @@ public void testLayoutRingBufferEventReusableMessageWithCurlyBraces() throws Exc .setCharset(StandardCharsets.UTF_8) .setIncludeStacktrace(true) .build(); - final Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE; + final Message message = factory.newMessage("Testing {}", new TestObj()); try { final RingBufferLogEvent ringBufferEvent = new RingBufferLogEvent(); ringBufferEvent.setValues( @@ -555,7 +564,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-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java index 0d90e165bdb..7e37e94923a 100644 --- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java +++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java @@ -26,6 +26,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.jackson.AbstractJacksonLayout; import org.apache.logging.log4j.jackson.XmlConstants; @@ -107,7 +108,7 @@ public LogEvent getLogEvent() { * @return an XML Layout. */ public static XmlLayout createDefaultLayout() { - return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, false, null); + return new XmlLayout(new DefaultConfiguration(), false, false, false, false, StandardCharsets.UTF_8, true, false, false, false, null); } @PluginFactory diff --git a/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java b/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java index 061a9ee2bb3..67c984812d1 100644 --- a/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java +++ b/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java @@ -16,14 +16,11 @@ */ package org.apache.logging.log4j.jackson.xml.layout; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; @@ -31,6 +28,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.core.lookup.JavaLookup; @@ -69,11 +67,11 @@ public static void cleanupClass() { Logger rootLogger = this.ctx.getRootLogger(); - private void checkAttribute(final String name, final String value, final boolean compact, final String str) { + private void checkAttribute(final String name, final String value, final String str) { assertTrue(str.contains(name + "=\"" + value + "\""), str); } - private void checkAttributeName(final String name, final boolean compact, final String str) { + private void checkAttributeName(final String name, final String str) { assertTrue(str.contains(name + "=\""), str); } @@ -87,7 +85,7 @@ private void checkContains(final String expected, final List list) { fail("Cannot find " + expected + " in " + list); } - private void checkContextMapElement(final String key, final String value, final boolean compact, final String str) { + private void checkContextMapElement(final String key, final String value, final String str) { // assertTrue(str.contains(String.format("", key, value)), str); } @@ -97,8 +95,8 @@ private void checkContextStackElement(final String value, final boolean compact, assertTrue(str.contains(String.format("%s", value)), str); } - private void checkElementName(final String name, final boolean compact, final String str, - final boolean withAttributes, final boolean withChildren) { + private void checkElementName(final String name, final String str, + final boolean withAttributes, final boolean withChildren) { // simple checks, don't try to be too smart here, we're just looking for the names and basic shape. // start final String startStr = withAttributes ? "<" + name + " " : "<" + name + ">"; @@ -154,6 +152,7 @@ private String prepareXMLForStacktraceTests(final boolean stacktraceAsString) { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); // @formatter:off final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setCompact(true) .setIncludeStacktrace(true) .setStacktraceAsString(stacktraceAsString) @@ -163,8 +162,11 @@ private String prepareXMLForStacktraceTests(final boolean stacktraceAsString) { } @Test - public void testAdditionalFields() throws Exception { - final AbstractJacksonLayout layout = XmlLayout.newBuilder().setLocationInfo(false).setProperties(false) + public void testAdditionalFields() { + final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setLocationInfo(false) + .setProperties(false) .setIncludeStacktrace(false) .setAdditionalFields(new KeyValuePair[] { new KeyValuePair("KEY1", "VALUE1"), new KeyValuePair("KEY2", "${java:runtime}"), }) @@ -175,8 +177,10 @@ public void testAdditionalFields() throws Exception { } @Test - public void testMutableLogEvent() throws Exception { - final AbstractJacksonLayout layout = XmlLayout.newBuilder().setLocationInfo(false).setProperties(false) + public void testMutableLogEvent() { + final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setLocationInfo(false).setProperties(false) .setIncludeStacktrace(false) .setAdditionalFields(new KeyValuePair[] { new KeyValuePair("KEY1", "VALUE1"), new KeyValuePair("KEY2", "${java:runtime}"), }) @@ -188,24 +192,14 @@ public void testMutableLogEvent() throws Exception { final String strMutableEvent = layout.toSerializable(mutableEvent); assertEquals(strLogEvent, strMutableEvent, strMutableEvent); } - /** - * @param includeLocationInfo - * TODO - * @param compact - * @param includeContextMap - * TODO - * @param includeContextStack - * TODO - * @throws IOException - * @throws JsonParseException - * @throws JsonMappingException - */ + private void testAllFeatures(final boolean includeLocationInfo, final boolean compact, final boolean includeContextMap, final boolean includeContextStack, final boolean includeStacktrace) - throws IOException, JsonParseException, JsonMappingException { + throws Exception { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); // @formatter:off final XmlLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setLocationInfo(includeLocationInfo) .setProperties(includeContextMap) .setComplete(false) @@ -223,8 +217,8 @@ private void testAllFeatures(final boolean includeLocationInfo, final boolean co LogEventFixtures.assertEqualLogEvents(expected, actual, includeLocationInfo, includeContextMap, includeStacktrace); if (includeContextMap) { - this.checkContextMapElement("MDC.A", "A_Value", compact, str); - this.checkContextMapElement("MDC.B", "B_Value", compact, str); + this.checkContextMapElement("MDC.A", "A_Value", str); + this.checkContextMapElement("MDC.B", "B_Value", str); } if (includeContextStack) { this.checkContextStackElement("stack_msg1", compact, str); @@ -247,50 +241,50 @@ private void testAllFeatures(final boolean includeLocationInfo, final boolean co // // make sure the names we want are used // this.checkAttributeName("timeMillis", compact, str); - this.checkElementName("Instant", compact, str, true, false); - this.checkAttributeName("epochSecond", compact, str); - this.checkAttributeName("nanoOfSecond", compact, str); - this.checkAttributeName("thread", compact, str); // and not threadName - this.checkAttributeName("level", compact, str); - this.checkAttributeName("loggerName", compact, str); - this.checkElementName("Marker", compact, str, true, true); - this.checkAttributeName("name", compact, str); - this.checkElementName("Parents", compact, str, false, true); - this.checkElementName("Message", compact, str, false, true); - this.checkElementName("Thrown", compact, str, true, true); - this.checkElementName("Cause", compact, str, true, includeStacktrace); - this.checkAttributeName("commonElementCount", compact, str); - this.checkAttributeName("message", compact, str); - this.checkAttributeName("localizedMessage", compact, str); + this.checkElementName("Instant", str, true, false); + this.checkAttributeName("epochSecond", str); + this.checkAttributeName("nanoOfSecond", str); + this.checkAttributeName("thread", str); // and not threadName + this.checkAttributeName("level", str); + this.checkAttributeName("loggerName", str); + this.checkElementName("Marker", str, true, true); + this.checkAttributeName("name", str); + this.checkElementName("Parents", str, false, true); + this.checkElementName("Message", str, false, true); + this.checkElementName("Thrown", str, true, true); + this.checkElementName("Cause", str, true, includeStacktrace); + this.checkAttributeName("commonElementCount", str); + this.checkAttributeName("message", str); + this.checkAttributeName("localizedMessage", str); if (includeStacktrace) { - this.checkElementName("ExtendedStackTrace", compact, str, false, true); - this.checkAttributeName("class", compact, str); - this.checkAttributeName("method", compact, str); - this.checkAttributeName("file", compact, str); - this.checkAttributeName("line", compact, str); - this.checkAttributeName("exact", compact, str); - this.checkAttributeName("location", compact, str); - this.checkAttributeName("version", compact, str); + this.checkElementName("ExtendedStackTrace", str, false, true); + this.checkAttributeName("class", str); + this.checkAttributeName("method", str); + this.checkAttributeName("file", str); + this.checkAttributeName("line", str); + this.checkAttributeName("exact", str); + this.checkAttributeName("location", str); + this.checkAttributeName("version", str); } else { this.checkElementNameAbsent("ExtendedStackTrace", compact, str); } - this.checkElementName("Suppressed", compact, str, false, true); - this.checkAttributeName("loggerFqcn", compact, str); - this.checkAttributeName("endOfBatch", compact, str); + this.checkElementName("Suppressed", str, false, true); + this.checkAttributeName("loggerFqcn", str); + this.checkAttributeName("endOfBatch", str); if (includeContextMap) { - this.checkElementName("ContextMap", compact, str, false, true); + this.checkElementName("ContextMap", str, false, true); } else { this.checkElementNameAbsent("ContextMap", compact, str); } - this.checkElementName("ContextStack", compact, str, false, true); + this.checkElementName("ContextStack", str, false, true); if (includeLocationInfo) { - this.checkElementName("Source", compact, str, true, false); + this.checkElementName("Source", str, true, false); } else { this.checkElementNameAbsent("Source", compact, str); } // check some attrs - this.checkAttribute("loggerFqcn", "f.q.c.n", compact, str); - this.checkAttribute("loggerName", "a.B", compact, str); + this.checkAttribute("loggerFqcn", "f.q.c.n", str); + this.checkAttribute("loggerName", "a.B", str); this.checkJsonPropertyOrder(includeContextStack, includeContextMap, includeStacktrace, str); } @@ -312,16 +306,20 @@ public void testExcludeStacktrace() throws Exception { } @Test - public void testIncludeNullDelimiterFalse() throws Exception { - final AbstractJacksonLayout layout = XmlLayout.newBuilder().setCompact(true).setIncludeNullDelimiter(false) + public void testIncludeNullDelimiterFalse() { + final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setCompact(true).setIncludeNullDelimiter(false) .build(); final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); assertFalse(str.endsWith("\0")); } @Test - public void testIncludeNullDelimiterTrue() throws Exception { - final AbstractJacksonLayout layout = XmlLayout.newBuilder().setCompact(true).setIncludeNullDelimiter(true) + public void testIncludeNullDelimiterTrue() { + final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setCompact(true).setIncludeNullDelimiter(true) .build(); final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); assertTrue(str.endsWith("\0")); @@ -331,13 +329,15 @@ public void testIncludeNullDelimiterTrue() throws Exception { * Test case for MDC conversion pattern. */ @Test - public void testLayout() throws Exception { + public void testLayout() { final Map appenders = this.rootLogger.getAppenders(); for (final Appender appender : appenders.values()) { this.rootLogger.removeAppender(appender); } // set up appender - final XmlLayout layout = XmlLayout.newBuilder().setLocationInfo(true).setProperties(true).setComplete(true) + final XmlLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setLocationInfo(true).setProperties(true).setComplete(true) .setCompact(false).setIncludeStacktrace(true).build(); final ListAppender appender = new ListAppender("List", null, layout, true, false); @@ -388,7 +388,9 @@ public void testLayout() throws Exception { @Test public void testLayoutLoggerName() { - final XmlLayout layout = XmlLayout.newBuilder().setLocationInfo(false).setProperties(true).setComplete(true) + final XmlLayout layout = XmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setLocationInfo(false).setProperties(true).setComplete(true) .setCompact(false).setIncludeStacktrace(true).build(); final Log4jLogEvent event = Log4jLogEvent.newBuilder() // @@ -418,13 +420,13 @@ public void testLocationOnCompactOnNdcOn() throws Exception { } @Test - public void testStacktraceAsNonString() throws Exception { + public void testStacktraceAsNonString() { final String str = prepareXMLForStacktraceTests(false); assertTrue(str.contains("java.lang.NullPointerException"), str); } diff --git a/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java b/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java index bcaa55df3bf..2fa54b4a644 100644 --- a/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java +++ b/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java @@ -101,6 +101,7 @@ private String prepareYAMLForStacktraceTests(final boolean stacktraceAsString) { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); // @formatter:off final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) .setIncludeStacktrace(true) .setStacktraceAsString(stacktraceAsString) .build(); @@ -149,6 +150,7 @@ private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace) throws Exception { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) .setLocationInfo(includeSource) .setProperties(includeContext) .setIncludeStacktrace(includeStacktrace) @@ -270,6 +272,7 @@ public void testExcludeStacktrace() throws Exception { @Test public void testIncludeNullDelimiterFalse() throws Exception { final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) .setIncludeNullDelimiter(false) .build(); final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); @@ -279,6 +282,7 @@ public void testIncludeNullDelimiterFalse() throws Exception { @Test public void testIncludeNullDelimiterTrue() throws Exception { final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) .setIncludeNullDelimiter(true) .build(); final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); @@ -338,6 +342,7 @@ public void testLayout() throws Exception { @Test public void testLayoutLoggerName() throws Exception { final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) .setLocationInfo(false) .setProperties(false) .setIncludeStacktrace(true) diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutConcurrentEncodeTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutConcurrentEncodeTest.java index aae0b565991..75952fc34a4 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutConcurrentEncodeTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutConcurrentEncodeTest.java @@ -35,7 +35,6 @@ import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; import org.apache.logging.log4j.core.layout.ByteBufferDestination; import org.apache.logging.log4j.core.layout.StringBuilderEncoder; -import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; @@ -177,7 +176,7 @@ private static Thread createWorker( // Determine the message to be logged. final String messageLetter = String.valueOf((char) ('A' + threadIndex)); - final String message = Strings.repeat(messageLetter, messageLength); + final String message = messageLetter.repeat(messageLength); // Create the worker thread. final String threadName = String.format("Worker-%d", threadIndex); diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java index 156d02697ee..a7e095b9e58 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java @@ -16,12 +16,7 @@ */ package org.apache.logging.log4j.layout.template.json; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.math.BigDecimal; import java.net.ServerSocket; import java.net.Socket; @@ -30,11 +25,7 @@ import java.time.Instant; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; @@ -59,24 +50,12 @@ import org.apache.logging.log4j.core.test.AvailablePortFinder; import org.apache.logging.log4j.core.time.MutableInstant; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; +import org.apache.logging.log4j.layout.template.json.resolver.*; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessageFactory; -import org.apache.logging.log4j.message.ReusableMessageFactory; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.message.*; import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.Test; import static org.apache.logging.log4j.layout.template.json.TestHelpers.*; @@ -720,7 +699,7 @@ void test_maxStringLength() { // Create the log event. final int maxStringLength = 30; - final String excessiveMessageString = Strings.repeat("m", maxStringLength) + 'M'; + final String excessiveMessageString = "m".repeat(maxStringLength) + 'M'; final SimpleMessage message = new SimpleMessage(excessiveMessageString); final Throwable thrown = new RuntimeException(); final LogEvent logEvent = Log4jLogEvent @@ -733,8 +712,8 @@ void test_maxStringLength() { // Create the event template node with map values. final String messageKey = "message"; - final String excessiveKey = Strings.repeat("k", maxStringLength) + 'K'; - final String excessiveValue = Strings.repeat("v", maxStringLength) + 'V'; + final String excessiveKey = "k".repeat(maxStringLength) + 'K'; + final String excessiveValue = "v".repeat(maxStringLength) + 'V'; final String nullValueKey = "nullValueKey"; final String eventTemplate = writeJson(asMap( messageKey, asMap("$resolver", "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 7486e9b5b3d..c1e0f9e90b9 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 @@ -45,7 +45,6 @@ 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.status.StatusLogger; import org.assertj.core.api.Assertions; @@ -123,7 +122,6 @@ class LogstashIT { .setConfiguration(CONFIGURATION) .setCharset(CHARSET) .setEventTemplateUri("classpath:EcsLayout.json") - .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance()) .setEventTemplateAdditionalFields( new EventTemplateAdditionalField[]{ EventTemplateAdditionalField 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 64850f4f707..6c3bd7e1633 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 @@ -26,12 +26,12 @@ 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; /** - * 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.ThreadLocalRecycler} 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/JsonWriterTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java index 888b9a5e204..a76a4d33227 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java @@ -20,13 +20,7 @@ import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -37,7 +31,6 @@ import org.apache.logging.log4j.util.SortedArrayStringMap; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.StringMap; -import org.apache.logging.log4j.util.Strings; import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; @@ -89,7 +82,7 @@ void test_close() { @Test void test_close_after_excessive_write() { withLockedWriter(writer -> { - final String text = Strings.repeat("x", writer.getMaxStringLength()); + final String text = "x".repeat(writer.getMaxStringLength()); writer.writeString(text); writer.writeString(text); writer.close(); @@ -472,7 +465,7 @@ void test_writeString_emitter() { void test_writeString_emitter_excessive_string() { withLockedWriter(writer -> { final int maxStringLength = writer.getMaxStringLength(); - final String excessiveString = Strings.repeat("x", maxStringLength) + 'y'; + final String excessiveString = "x".repeat(maxStringLength) + 'y'; final String expectedJson = '"' + excessiveString.substring(0, maxStringLength) + writer.getTruncatedStringSuffix() + @@ -491,12 +484,12 @@ void test_writeString_emitter_excessive_string_ending_with_high_surrogate() { final int maxStringLength = writer.getMaxStringLength(); @SuppressWarnings("StringBufferReplaceableByString") final String excessiveString = new StringBuilder() - .append(Strings.repeat("x", maxStringLength - 1)) + .append("x".repeat(maxStringLength - 1)) .append(HI_SURROGATE) .append(LO_SURROGATE) .toString(); final String expectedJson = "\"" + - Strings.repeat("x", maxStringLength - 1) + + "x".repeat(maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"'; final BiConsumer emitter = StringBuilder::append; @@ -526,7 +519,7 @@ void test_writeString_formattable() { void test_writeString_formattable_excessive_string() { withLockedWriter(writer -> { final int maxStringLength = writer.getMaxStringLength(); - final String excessiveString = Strings.repeat("x", maxStringLength) + 'y'; + final String excessiveString = "x".repeat(maxStringLength) + 'y'; final String expectedJson = '"' + excessiveString.substring(0, maxStringLength) + writer.getTruncatedStringSuffix() + @@ -545,12 +538,12 @@ void test_writeString_formattable_excessive_string_ending_with_high_surrogate() final int maxStringLength = writer.getMaxStringLength(); @SuppressWarnings("StringBufferReplaceableByString") final String excessiveString = new StringBuilder() - .append(Strings.repeat("x", maxStringLength - 1)) + .append("x".repeat(maxStringLength - 1)) .append(HI_SURROGATE) .append(LO_SURROGATE) .toString(); final String expectedJson = "\"" + - Strings.repeat("x", maxStringLength - 1) + + "x".repeat(maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"'; final String actualJson = writer.use(() -> @@ -612,9 +605,9 @@ void test_writeString_seq_negative_length() { @Test void test_writeString_excessive_seq() { withLockedWriter(writer -> { - final CharSequence seq = Strings.repeat("x", writer.getMaxStringLength()) + 'y'; + final CharSequence seq = "x".repeat(writer.getMaxStringLength()) + 'y'; final String expectedJson = "\"" + - Strings.repeat("x", writer.getMaxStringLength()) + + "x".repeat(writer.getMaxStringLength()) + writer.getTruncatedStringSuffix() + '"'; final String actualJson = writer.use(() -> writer.writeString(seq)); @@ -628,12 +621,12 @@ void test_writeString_excessive_seq_ending_with_high_surrogate() { final int maxStringLength = writer.getMaxStringLength(); @SuppressWarnings("StringBufferReplaceableByString") final CharSequence seq = new StringBuilder() - .append(Strings.repeat("x", maxStringLength - 1)) + .append("x".repeat(maxStringLength - 1)) .append(HI_SURROGATE) .append(LO_SURROGATE) .toString(); final String expectedJson = "\"" + - Strings.repeat("x", maxStringLength - 1) + + "x".repeat(maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"'; final String actualJson = writer.use(() -> writer.writeString(seq)); @@ -685,10 +678,10 @@ void test_writeString_buffer_negative_length() { void test_writeString_excessive_buffer() { withLockedWriter(writer -> { final char[] buffer = - (Strings.repeat("x", writer.getMaxStringLength()) + 'y') + ("x".repeat(writer.getMaxStringLength()) + 'y') .toCharArray(); final String expectedJson = "\"" + - Strings.repeat("x", writer.getMaxStringLength()) + + "x".repeat(writer.getMaxStringLength()) + writer.getTruncatedStringSuffix() + '"'; final String actualJson = writer.use(() -> writer.writeString(buffer)); @@ -702,13 +695,13 @@ void test_writerString_excessive_buffer_ending_with_high_surrogate() { final int maxStringLength = writer.getMaxStringLength(); @SuppressWarnings("StringBufferReplaceableByString") final char[] buffer = new StringBuilder() - .append(Strings.repeat("x", maxStringLength - 1)) + .append("x".repeat(maxStringLength - 1)) .append(HI_SURROGATE) .append(LO_SURROGATE) .toString() .toCharArray(); final String expectedJson = "\"" + - Strings.repeat("x", maxStringLength - 1) + + "x".repeat(maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"'; final String actualJson = writer.use(() -> writer.writeString(buffer)); 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/layout/template/json/util/RecyclerFactoriesTest.java deleted file mode 100644 index 606790d380d..00000000000 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java +++ /dev/null @@ -1,139 +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.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; -import org.apache.logging.log4j.core.test.junit.Named; -import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout; -import org.apache.logging.log4j.plugins.convert.TypeConverter; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; -import org.apache.logging.log4j.plugins.di.DI; -import org.assertj.core.api.Assertions; -import org.jctools.queues.MpmcArrayQueue; -import org.junit.jupiter.api.Test; - -class RecyclerFactoriesTest { - - @Test - void test_RecyclerFactoryConverter() throws Exception { - - final ConfigurableInstanceFactory instanceFactory = DI.createInitializedFactory(); - // Check if the type converter is registered. - final TypeConverter converter = instanceFactory.getTypeConverter(RecyclerFactory.class); - Assertions.assertThat(converter).isNotNull(); - - // Check dummy recycler factory. - { - final Object actualDummyRecyclerFactory = converter.convert("dummy"); - Assertions - .assertThat(actualDummyRecyclerFactory) - .isSameAs(DummyRecyclerFactory.getInstance()); - } - - // Check thread-local recycler factory. - { - final Object actualThreadLocalRecyclerFactory = converter.convert("threadLocal"); - Assertions - .assertThat(actualThreadLocalRecyclerFactory) - .isSameAs(ThreadLocalRecyclerFactory.getInstance()); - } - - // Check queueing recycler factory. - { - final Object actualQueueingRecyclerFactory = converter.convert("queue"); - Assertions - .assertThat(actualQueueingRecyclerFactory) - .isInstanceOf(QueueingRecyclerFactory.class); - } - - // Check queueing recycler factory with supplier. - { - final Object recyclerFactory = converter.convert( - "queue:supplier=java.util.ArrayDeque.new"); - Assertions - .assertThat(recyclerFactory) - .isInstanceOf(QueueingRecyclerFactory.class); - final QueueingRecyclerFactory queueingRecyclerFactory = - (QueueingRecyclerFactory) recyclerFactory; - final Recycler recycler = - queueingRecyclerFactory.create(Object::new); - Assertions - .assertThat(recycler) - .isInstanceOf(QueueingRecycler.class); - final QueueingRecycler queueingRecycler = - (QueueingRecycler) recycler; - Assertions - .assertThat(queueingRecycler.getQueue()) - .isInstanceOf(ArrayDeque.class); - } - - // Check queueing recycler factory with capacity. - { - final Object actualQueueingRecyclerFactory = converter.convert( - "queue:capacity=100"); - Assertions - .assertThat(actualQueueingRecyclerFactory) - .isInstanceOf(QueueingRecyclerFactory.class); - } - - // Check queueing recycler factory with supplier and capacity. - { - final Object recyclerFactory = converter.convert( - "queue:" + - "supplier=java.util.concurrent.ArrayBlockingQueue.new," + - "capacity=100"); - Assertions - .assertThat(recyclerFactory) - .isInstanceOf(QueueingRecyclerFactory.class); - final QueueingRecyclerFactory queueingRecyclerFactory = - (QueueingRecyclerFactory) recyclerFactory; - final Recycler recycler = - queueingRecyclerFactory.create(Object::new); - Assertions - .assertThat(recycler) - .isInstanceOf(QueueingRecycler.class); - final QueueingRecycler queueingRecycler = - (QueueingRecycler) recycler; - Assertions - .assertThat(queueingRecycler.getQueue()) - .isInstanceOf(ArrayBlockingQueue.class); - final ArrayBlockingQueue queue = - (ArrayBlockingQueue) queueingRecycler.getQueue(); - Assertions.assertThat(queue.remainingCapacity()).isEqualTo(100); - } - - } - - @Test - @LoggerContextSource("recyclerFactoryCustomizedJsonTemplateLayoutLogging.xml") - void test_RecyclerFactoryConverter_using_XML_config( - final @Named(value = "List") ListAppender appender) - throws Exception { - final JsonTemplateLayout layout = (JsonTemplateLayout) appender.getLayout(); - final Field field = JsonTemplateLayout.class.getDeclaredField("contextRecycler"); - field.setAccessible(true); - final QueueingRecycler contextRecycler = (QueueingRecycler) field.get(layout); - final MpmcArrayQueue queue = (MpmcArrayQueue) contextRecycler.getQueue(); - Assertions.assertThat(queue.capacity()).isEqualTo(512); - } - -} 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 059c18da35f..4a5f448fe3a 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,11 +16,7 @@ */ 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; @@ -33,16 +29,13 @@ import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.layout.ByteBufferDestination; import org.apache.logging.log4j.core.layout.Encoder; -import org.apache.logging.log4j.core.layout.TextEncoderHelper; -import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.layout.StringBuilderEncoder; 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.status.StatusLogger; +import org.apache.logging.log4j.spi.Recycler; import org.apache.logging.log4j.util.Strings; @Configurable(elementType = Layout.ELEMENT_TYPE) @@ -93,11 +86,7 @@ private JsonTemplateLayout(final Builder builder) { .setMaxStringLength(builder.maxStringLength) .setTruncatedStringSuffix(builder.truncatedStringSuffix) .build(); - this.eventResolver = createEventResolver( - builder, - configuration, - charset, - jsonWriter); + this.eventResolver = createEventResolver(builder, configuration, charset, jsonWriter); this.contextRecycler = createContextRecycler(builder, jsonWriter); } @@ -148,7 +137,6 @@ private TemplateResolver createEventResolver( .setSubstitutor(substitutor) .setCharset(charset) .setJsonWriter(jsonWriter) - .setRecyclerFactory(builder.recyclerFactory) .setMaxStringByteCount(maxStringByteCount) .setTruncatedStringSuffix(builder.truncatedStringSuffix) .setLocationInfoEnabled(builder.locationInfoEnabled) @@ -186,14 +174,9 @@ private static String readTemplate( : template; } - private static Recycler createContextRecycler( - final Builder builder, - final JsonWriter jsonWriter) { - final Supplier supplier = - createContextSupplier(builder.charset, jsonWriter); - return builder - .recyclerFactory - .create(supplier, Context::close); + private static Recycler createContextRecycler(final Builder builder, final JsonWriter jsonWriter) { + final Supplier supplier = createContextSupplier(builder.charset, jsonWriter); + return builder.configuration.getRecyclerFactory().create(supplier, Context::close); } private static Supplier createContextSupplier( @@ -206,54 +189,6 @@ private static Supplier createContextSupplier( }; } - /** - * {@link org.apache.logging.log4j.core.layout.StringBuilderEncoder} clone replacing thread-local allocations with instance fields. - */ - private static final class StringBuilderEncoder implements Encoder { - - private final Charset charset; - - private final CharsetEncoder charsetEncoder; - - private final CharBuffer charBuffer; - - private final ByteBuffer byteBuffer; - - private StringBuilderEncoder(final Charset charset) { - this.charset = charset; - this.charsetEncoder = charset - .newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - this.charBuffer = CharBuffer.allocate(Constants.ENCODER_CHAR_BUFFER_SIZE); - this.byteBuffer = ByteBuffer.allocate(Constants.ENCODER_BYTE_BUFFER_SIZE); - } - - @Override - public void encode( - final StringBuilder source, - final ByteBufferDestination destination) { - try { - TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination); - } catch (final Exception error) { - fallbackEncode(charset, source, destination, error); - } - } - - private /* for JIT-ergonomics: */ static void fallbackEncode( - final Charset charset, - final StringBuilder source, - final ByteBufferDestination destination, - final Exception error) { - StatusLogger - .getLogger() - .error("TextEncoderHelper.encodeText() failure", error); - final byte[] bytes = source.toString().getBytes(charset); - destination.writeBytes(bytes, 0, bytes.length); - } - - } - @Override public byte[] toByteArray(final LogEvent event) { final String eventJson = toSerializable(event); @@ -395,10 +330,6 @@ public static final class Builder private String truncatedStringSuffix = JsonTemplateLayoutDefaults.getTruncatedStringSuffix(); - @PluginBuilderAttribute - private RecyclerFactory recyclerFactory = - JsonTemplateLayoutDefaults.getRecyclerFactory(); - private Builder() { // Do nothing. } @@ -533,15 +464,6 @@ public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) { return this; } - public RecyclerFactory getRecyclerFactory() { - return recyclerFactory; - } - - public Builder setRecyclerFactory(final RecyclerFactory recyclerFactory) { - this.recyclerFactory = recyclerFactory; - return this; - } - @Override public JsonTemplateLayout build() { validate(); @@ -549,7 +471,7 @@ public JsonTemplateLayout build() { } private void validate() { - Objects.requireNonNull(configuration, "config"); + Objects.requireNonNull(configuration, "configuration"); if (Strings.isBlank(eventTemplate) && Strings.isBlank(eventTemplateUri)) { throw new IllegalArgumentException( "both eventTemplate and eventTemplateUri are blank"); @@ -566,7 +488,6 @@ private void validate() { maxStringLength); } Objects.requireNonNull(truncatedStringSuffix, "truncatedStringSuffix"); - Objects.requireNonNull(recyclerFactory, "recyclerFactory"); } } 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 b8a9add7134..230da760521 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,6 @@ 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.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; @@ -138,10 +136,4 @@ public static String getTruncatedStringSuffix() { "…"); } - public static RecyclerFactory getRecyclerFactory() { - final String recyclerFactorySpec = PROPERTIES.getStringProperty( - "log4j.layout.jsonTemplate.recyclerFactory"); - return RecyclerFactories.ofSpec(recyclerFactorySpec); - } - } 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 db0d25cbe15..7d51ef7b1d3 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 @@ -24,7 +24,7 @@ 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.spi.Recycler; /** * Resolves a number from an internal counter. @@ -145,6 +145,7 @@ private static Consumer createBigIntegerResolver(final BigInteger st private static Recycler createStringBuilderRecycler( final EventResolverContext context) { return context + .getConfiguration() .getRecyclerFactory() .create( StringBuilder::new, 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 b0fa83509e9..b2783d65585 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 @@ -25,7 +25,6 @@ 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.util.Strings; /** @@ -48,8 +47,6 @@ public final class EventResolverContext implements TemplateResolverContext 0: " + 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 0af62b2c4f9..3d5ab759f85 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. @@ -83,6 +83,7 @@ public final class MessageParameterResolver implements EventResolver { final EventResolverContext context, final TemplateResolverConfig config) { this.parameterConsumerStateRecycler = context + .getConfiguration() .getRecyclerFactory() .create(ParameterConsumerState::new); this.stringified = config.getBoolean("stringified", false); 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 84ef13183f5..618f030ca90 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 @@ -23,8 +23,8 @@ 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.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.TriConsumer; @@ -212,7 +212,7 @@ private static EventResolver createResolver( if (key != null) { return createKeyResolver(key, stringified, mapAccessor); } else { - final RecyclerFactory recyclerFactory = context.getRecyclerFactory(); + final RecyclerFactory recyclerFactory = context.getConfiguration().getRecyclerFactory(); return createResolver( recyclerFactory, flatten, 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 6feace011d0..c51e5232f7c 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 @@ -23,7 +23,11 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.apache.logging.log4j.layout.template.json.util.*; +import org.apache.logging.log4j.layout.template.json.util.CharSequencePointer; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter; +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}. @@ -52,7 +56,7 @@ final class StackTraceStringResolver implements StackTraceResolver { final Supplier writerSupplier = () -> TruncatingBufferedPrintWriter.ofCapacity( context.getMaxStringByteCount()); - final RecyclerFactory recyclerFactory = context.getRecyclerFactory(); + final RecyclerFactory recyclerFactory = context.getConfiguration().getRecyclerFactory(); this.srcWriterRecycler = recyclerFactory.create( writerSupplier, TruncatingBufferedPrintWriter::close); 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 bdc73d0d0df..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 99cce75cb1d..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 d5041a5dce1..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/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 35990d499bf..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 b85b42c7efb..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 final 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/BlackHoleByteBufferDestination.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java index 9775b8db514..23e17fcd603 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java @@ -19,6 +19,7 @@ import java.nio.ByteBuffer; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; class BlackHoleByteBufferDestination implements ByteBufferDestination { @@ -46,12 +47,12 @@ public ByteBuffer drain(final ByteBuffer byteBuffer) { @Override public void writeBytes(final ByteBuffer byteBuffer) { - unsynchronizedWrite(byteBuffer); + ByteBufferDestinationHelper.writeToUnsynchronized(byteBuffer, this); } @Override public void writeBytes(final byte[] buffer, final int offset, final int length) { - unsynchronizedWrite(buffer, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(buffer, offset, length, this); } } diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkReport.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkReport.java index 1cfb2444a53..caa21adc2c6 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkReport.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkReport.java @@ -38,7 +38,6 @@ import java.util.stream.Collectors; import org.apache.logging.log4j.util.JsonReader; -import org.apache.logging.log4j.util.Strings; /** * Utility class to summarize {@link JsonTemplateLayoutBenchmark} results in Asciidoctor. @@ -362,7 +361,7 @@ private static void dumpJmhSummary( .toBigInteger() .add(BigInteger.ONE) .intValueExact(); - final String opRateBar = Strings.repeat("▉", opRateBarLength); + final String opRateBar = "▉".repeat(opRateBarLength); final int opRatePercent = normalizedOpRate .multiply(BigDecimal.valueOf(100)) .toBigInteger() 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 c167a95c046..f9620d8387e 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 @@ -31,7 +31,6 @@ 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.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; @@ -85,7 +84,6 @@ private static JsonTemplateLayout createJtl4JsonLayout() { .setConfiguration(CONFIGURATION) .setCharset(CHARSET) .setEventTemplateUri("classpath:JsonLayout.json") - .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance()) .build(); } @@ -103,7 +101,6 @@ private static JsonTemplateLayout createJtl4EcsLayout() { .setConfiguration(CONFIGURATION) .setCharset(CHARSET) .setEventTemplateUri("classpath:EcsLayout.json") - .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance()) .setEventTemplateAdditionalFields(additionalFields) .build(); } @@ -114,7 +111,6 @@ private static JsonTemplateLayout createJtl4GelfLayout() { .setConfiguration(CONFIGURATION) .setCharset(CHARSET) .setEventTemplateUri("classpath:GelfLayout.json") - .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance()) .setEventTemplateAdditionalFields( new EventTemplateAdditionalField[]{ // Adding "host" as a constant rather than using 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 128e0ac1ba5..8e64e67ee50 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 @@ -24,9 +24,11 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.AbstractStringLayout; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; import org.apache.logging.log4j.core.layout.Encoder; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; @@ -203,7 +205,7 @@ private static long consume(final byte[] bytes, final int offset, final int limi private static class GetBytesLayout extends AbstractStringLayout { public GetBytesLayout(final Charset charset) { - super(charset); + super(new DefaultConfiguration(), charset); } @Override @@ -213,15 +215,19 @@ 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 = stringBuilderRecycler.acquire(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + return getBytes(sb.toString()); + } finally { + stringBuilderRecycler.release(sb); + } } } private static class EncodeLayout extends AbstractStringLayout { public EncodeLayout(final Charset charset) { - super(charset); + super(new DefaultConfiguration(), charset); } @Override @@ -236,10 +242,18 @@ 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 = stringBuilderRecycler.acquire(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + final Encoder helper = stringBuilderEncoderRecycler.acquire(); + try { + helper.encode(sb, destination); + } finally { + stringBuilderEncoderRecycler.release(helper); + } + } finally { + stringBuilderRecycler.release(sb); + } } } @@ -261,12 +275,12 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } @Override public void writeBytes(final byte[] data, final int offset, final int length) { - unsynchronizedWrite(data, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(data, offset, length, this); } public void reset() { diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java index 74b49fd72b0..3c192a3996a 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java @@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.layout.StringBuilderEncoder; import org.apache.logging.log4j.message.Message; @@ -95,12 +96,12 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } @Override public void writeBytes(final byte[] data, final int offset, final int length) { - unsynchronizedWrite(data, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(data, offset, length, this); } } diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java index 851bc56f4e2..6a7e6ff5a0b 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; /** * Demo Appender that does not do any I/O. @@ -72,11 +73,11 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } @Override public void writeBytes(final byte[] data, final int offset, final int length) { - unsynchronizedWrite(data, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(data, offset, length, this); } } diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java index 6efab887070..1e7e5850a68 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; import org.apache.logging.log4j.core.util.Constants; /** @@ -63,12 +64,12 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { - unsynchronizedWrite(data); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } @Override public void writeBytes(final byte[] data, final int offset, final int length) { - unsynchronizedWrite(data, offset, length); + ByteBufferDestinationHelper.writeToUnsynchronized(data, offset, length, this); } private void consume(final byte[] data, final int offset, final int length) { diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java index c8850c0b826..4afac1be145 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java @@ -27,6 +27,8 @@ import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.plugins.Singleton; import org.apache.logging.log4j.plugins.util.TypeUtil; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Cast; import org.apache.logging.log4j.util.EnglishEnums; @@ -58,6 +60,7 @@ public TypeConverterFactory(@TypeConverters final List> typeCon registerTypeAlias(Integer.class, Integer.TYPE); registerTypeConverter(Long.class, Long::valueOf); registerTypeAlias(Long.class, Long.TYPE); + registerTypeConverter(RecyclerFactory.class, RecyclerFactories::ofSpec); registerTypeConverter(Short.class, Short::valueOf); registerTypeAlias(Short.class, Short.TYPE); registerTypeConverter(String.class, s -> s); diff --git a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java index 2c9d8dfcfe8..7caf33f305c 100644 --- a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java +++ b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.test.AvailablePortFinder; import org.apache.logging.log4j.core.test.categories.Appenders; import org.apache.logging.log4j.smtp.MimeMessageBuilder; @@ -115,6 +116,7 @@ public void testDelivery() { ThreadContext.put(subjectKey, subjectValue); final int smtpPort = AvailablePortFinder.getNextAvailable(); final SmtpAppender appender = SmtpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setName("Test") .setTo("to@example.com") .setCc("cc@example.com") diff --git a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java index 9779480b171..65ceccc1f33 100644 --- a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java +++ b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpManagerTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.RingBufferLogEvent; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.MementoMessage; import org.apache.logging.log4j.core.impl.MutableLogEvent; @@ -45,7 +46,7 @@ void testCreateManagerName() { } private void testAdd(final LogEvent event) { - final SmtpManager smtpManager = SmtpManager.getSmtpManager(null, "to", "cc", "bcc", "from", "replyTo", "subject", "protocol", "host", 0, "username", "password", false, "filterName", 10, null); + final SmtpManager smtpManager = SmtpManager.getSmtpManager(new DefaultConfiguration(), "to", "cc", "bcc", "from", "replyTo", "subject", "protocol", "host", 0, "username", "password", false, "filterName", 10, null); smtpManager.removeAllBufferedEvents(); // in case this smtpManager is reused smtpManager.add(event); diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java index c34c2f281f1..acf77d6c0c9 100644 --- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java +++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java @@ -23,7 +23,8 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.spi.Recycler; import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; import org.slf4j.spi.LocationAwareLogger; @@ -33,16 +34,18 @@ */ public class SLF4JLogger extends AbstractLogger { - private static final long serialVersionUID = 1L; /** * Logback supports turbo filters, that can override the logger's level. * Therefore we can never return a no-op builder. */ private static final boolean LAZY_LEVEL_CHECK = "ch.qos.logback.classic.LoggerContext" .equals(LoggerFactory.getILoggerFactory().getClass().getName()); - private static final ThreadLocal logBuilder = ThreadLocal.withInitial(SLF4JLogBuilder::new); + + private static final Recycler LOG_BUILDER_RECYCLER = + LoggingSystem.getRecyclerFactory().create(SLF4JLogBuilder::new); private final org.slf4j.Logger logger; + private final LocationAwareLogger locationAwareLogger; public SLF4JLogger(final String name, final MessageFactory messageFactory, final org.slf4j.Logger logger) { @@ -307,9 +310,8 @@ public LogBuilder atFatal() { @Override protected LogBuilder getLogBuilder(final Level level) { - final SLF4JLogBuilder builder = logBuilder.get(); - return Constants.isThreadLocalsEnabled() && !builder.isInUse() ? builder.reset(this, level) - : new SLF4JLogBuilder(this, level); + final SLF4JLogBuilder builder = LOG_BUILDER_RECYCLER.acquire(); + return builder.reset(this, level); } @Override @@ -320,4 +322,5 @@ public LogBuilder atLevel(final Level level) { } return super.atLevel(level); } + }