From b69adc2bc839591f6bb27432c864edd4e0668303 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Mon, 9 Jan 2023 12:06:58 -0600 Subject: [PATCH 01/39] Apply spotless automatically This adds executions during process-sources and process-test-sources to ensure that modified files are formatted properly. --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 9510be00de1..2b8cd3e4da1 100644 --- a/pom.xml +++ b/pom.xml @@ -1655,6 +1655,19 @@ check + + apply-spotless + + apply + + process-sources + + apply-spotless-test + + apply + + process-test-sources + From b46bc30055f65bbbbb4d7f1e91d7f5c839deec9b Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Mon, 9 Jan 2023 12:36:44 -0600 Subject: [PATCH 02/39] Migrate Recycler API to log4j-api This will allow for reuse of this API in places where ThreadLocal-recycled objects are currently used. This will be beneficial to runtimes where workloads don't correspond to threads such as virtual threads, coroutines, and reactive streams. This adds an optional dependency on JCTools to log4j-api for one of the QueueingRecycler implementations. --- log4j-api/pom.xml | 9 +-- log4j-api/src/main/java/module-info.java | 4 ++ .../logging/log4j}/util/DummyRecycler.java | 7 ++- .../log4j}/util/DummyRecyclerFactory.java | 2 +- .../logging/log4j}/util/QueueingRecycler.java | 2 +- .../log4j}/util/QueueingRecyclerFactory.java | 2 +- .../apache/logging/log4j}/util/Recycler.java | 20 +++++- .../log4j}/util/RecyclerFactories.java | 61 ++++++++++++------- .../logging/log4j/util/RecyclerFactory.java | 59 ++++++++++++++++++ .../log4j}/util/StringParameterParser.java | 6 +- .../log4j}/util/ThreadLocalRecycler.java | 9 ++- .../util/ThreadLocalRecyclerFactory.java | 2 +- .../layout/template/json/LogstashIT.java | 37 +++++------ .../ThreadLocalRecyclerNestedLoggingTest.java | 12 ++-- .../json => }/util/RecyclerFactoriesTest.java | 11 ++-- .../util/StringParameterParserTest.java | 19 +++--- .../template/json/JsonTemplateLayout.java | 24 ++++---- .../json/JsonTemplateLayoutDefaults.java | 4 +- .../json/resolver/CounterResolver.java | 8 +-- .../json/resolver/EventResolverContext.java | 12 ++-- .../resolver/MessageParameterResolver.java | 2 +- .../resolver/ReadOnlyStringMapResolver.java | 16 ++--- .../resolver/StackTraceStringResolver.java | 6 +- .../template/json/util/RecyclerFactory.java | 31 ---------- .../json/util/RecyclerFactoryConverter.java | 2 + .../JsonTemplateLayoutBenchmarkState.java | 11 ++-- 26 files changed, 232 insertions(+), 146 deletions(-) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/DummyRecycler.java (87%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/DummyRecyclerFactory.java (95%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/QueueingRecycler.java (96%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/QueueingRecyclerFactory.java (96%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/Recycler.java (57%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/RecyclerFactories.java (79%) create mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/StringParameterParser.java (99%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/ThreadLocalRecycler.java (79%) rename {log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json => log4j-api/src/main/java/org/apache/logging/log4j}/util/ThreadLocalRecyclerFactory.java (95%) rename log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/{layout/template/json => }/util/RecyclerFactoriesTest.java (98%) rename log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/{layout/template/json => }/util/StringParameterParserTest.java (96%) delete mode 100644 log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml index 714dea6c2b0..c83b0de9df5 100644 --- a/log4j-api/pom.xml +++ b/log4j-api/pom.xml @@ -43,6 +43,11 @@ org.osgi.resource provided + + org.jctools + jctools-core + true + @@ -52,10 +57,6 @@ org.apache.logging.log4j.* - - sun.reflect;resolution:=optional, - * - org.apache.logging.log4j.util.Activator <_fixupmessages>"Classes found in the wrong directory";is:=warning diff --git a/log4j-api/src/main/java/module-info.java b/log4j-api/src/main/java/module-info.java index 57f500e63e1..bb8d5dc0a80 100644 --- a/log4j-api/src/main/java/module-info.java +++ b/log4j-api/src/main/java/module-info.java @@ -38,8 +38,12 @@ exports org.apache.logging.log4j.status; exports org.apache.logging.log4j.util; + // optional support for formatting SQL date/time classes in messages properly requires static java.sql; + // optional support for running in an OSGi environment requires static org.osgi.framework; + // optional support for using JCTools in a Recycler + requires static org.jctools.core; uses org.apache.logging.log4j.spi.Provider; uses PropertySource; uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory; diff --git a/log4j-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/util/DummyRecycler.java similarity index 87% 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/util/DummyRecycler.java index 8936a8a682c..b8e6699c3c9 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/util/DummyRecycler.java @@ -14,10 +14,15 @@ * 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.Supplier; +/** + * Recycler strategy which doesn't recycle anything; all instances are freshly created. + * + * @param the recyclable type + */ public class DummyRecycler implements Recycler { private final Supplier supplier; 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/util/DummyRecyclerFactory.java similarity index 95% 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/util/DummyRecyclerFactory.java index 2a96d98fb84..2430efecf3f 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/util/DummyRecyclerFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.util; import java.util.function.Consumer; import java.util.function.Supplier; diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java similarity index 96% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java index b83ae2f3033..e6a422f7337 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.util; import java.util.Queue; import java.util.function.Consumer; diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java similarity index 96% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java index 85b04ab7e3c..19b485c45b4 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/QueueingRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.util; import java.util.Queue; import java.util.function.Consumer; diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Recycler.java similarity index 57% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/Recycler.java index 6e6924b366b..1a41b03827d 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Recycler.java @@ -14,12 +14,30 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.util; +/** + * Strategy for recycling objects. This is primarily useful for heavyweight objects and buffers. + * + * @param the recyclable type + * @since 3.0.0 + */ 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(); + /** + * 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/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java similarity index 79% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java index 2b4d236758f..581367a250d 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java @@ -14,7 +14,9 @@ * 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 org.jctools.queues.MpmcArrayQueue; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -25,30 +27,46 @@ 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 String JCTOOLS_FACTORY_CLASS_NAME = + RecyclerFactories.class.getName() + "$MpmcArrayQueueFactory"; + + private interface QueueFactory { + Queue create(final int capacity); + } + + private static class ArrayBlockingQueueFactory implements QueueFactory { + @Override + public Queue create(final int capacity) { + return new ArrayBlockingQueue<>(capacity); + } + } - private static final boolean JCTOOLS_QUEUE_CLASS_AVAILABLE = - isJctoolsQueueClassAvailable(); + @SuppressWarnings("unused") // loaded via reflection to check for presence of JCTools + private static class MpmcArrayQueueFactory implements QueueFactory { + @Override + public Queue create(final int capacity) { + return new MpmcArrayQueue<>(capacity); + } + } - private static boolean isJctoolsQueueClassAvailable() { + private static Supplier> getQueueSupplier(final int capacity) { + final ClassLoader classLoader = RecyclerFactories.class.getClassLoader(); + Class factoryClass; try { - final String className = JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH - .replaceAll("\\.new$", ""); - LoaderUtil.loadClass(className); - return true; - } catch (final ClassNotFoundException ignored) { - return false; + // try to load RecyclerFactories.MpmcArrayQueueFactory; a linkage error should occur if JCTools is unavailable + factoryClass = classLoader.loadClass(JCTOOLS_FACTORY_CLASS_NAME) + .asSubclass(QueueFactory.class); + } catch (final ClassNotFoundException | LinkageError ignored) { + factoryClass = ArrayBlockingQueueFactory.class; } + final QueueFactory queueFactory = ReflectionUtil.instantiate(factoryClass); + return () -> queueFactory.create(capacity); } public static RecyclerFactory ofSpec(final String recyclerFactorySpec) { @@ -63,11 +81,7 @@ public static RecyclerFactory ofSpec(final String recyclerFactorySpec) { if (isThreadLocalsEnabled()) { return ThreadLocalRecyclerFactory.getInstance(); } else { - final Supplier> queueSupplier = - JCTOOLS_QUEUE_CLASS_AVAILABLE - ? () -> new MpmcArrayQueue<>(defaultCapacity) - : () -> new ArrayBlockingQueue<>(defaultCapacity); - return new QueueingRecyclerFactory(queueSupplier); + return new QueueingRecyclerFactory(getQueueSupplier(defaultCapacity)); } } @@ -113,9 +127,7 @@ private static RecyclerFactory readQueueingRecyclerFactory( 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"; + supplierPath = null; } else { supplierPath = supplierValue.toString(); } @@ -136,6 +148,9 @@ private static RecyclerFactory readQueueingRecyclerFactory( } // Execute the read spec. + if (supplierPath == null) { + return new QueueingRecyclerFactory(getQueueSupplier(capacity)); + } return createRecyclerFactory(queueFactorySpec, supplierPath, capacity); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java new file mode 100644 index 00000000000..eab78a702d3 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/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.util; + +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-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java similarity index 99% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java index c2333a588eb..653877c1f49 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParser.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java @@ -14,9 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; - -import org.apache.logging.log4j.util.Strings; +package org.apache.logging.log4j.util; import java.util.Collections; import java.util.LinkedHashMap; @@ -25,6 +23,8 @@ import java.util.Set; import java.util.concurrent.Callable; +import org.apache.logging.log4j.util.Strings; + public final class StringParameterParser { private StringParameterParser() {} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java similarity index 79% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java index 99787553373..98957bffde6 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.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.util; import java.util.function.Consumer; import java.util.function.Supplier; +/** + * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy + * may not be appropriate in workloads where units of work are independent of operating system threads such as + * reactive streams, coroutines, or virtual threads. + * + * @param the recyclable type + */ public class ThreadLocalRecycler implements Recycler { private final Consumer cleaner; diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java similarity index 95% rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java index 048262cf4e0..c877b4d24b7 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/ThreadLocalRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.util; import java.util.function.Consumer; import java.util.function.Supplier; 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 9e460f6e5fc..b657e5bae57 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 @@ -16,7 +16,23 @@ */ package org.apache.logging.log4j.layout.template.json; -import co.elastic.logging.log4j2.EcsLayout; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.http.HttpHost; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; @@ -28,9 +44,9 @@ 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.apache.logging.log4j.util.ThreadLocalRecyclerFactory; import org.assertj.core.api.Assertions; import org.awaitility.Awaitility; import org.elasticsearch.ElasticsearchStatusException; @@ -52,22 +68,7 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import co.elastic.logging.log4j2.EcsLayout; @Execution(ExecutionMode.SAME_THREAD) class LogstashIT { diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java index ec97185074d..1dfc7acd719 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java @@ -16,20 +16,20 @@ */ package org.apache.logging.log4j.layout.template.json; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.core.test.junit.Named; -import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecycler; +import org.apache.logging.log4j.util.ThreadLocalRecycler; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Tests if logging while trying to encode an event causes {@link ThreadLocalRecycler} to incorrectly share buffers and end up overriding layout's earlier encoding work. * 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/util/RecyclerFactoriesTest.java similarity index 98% rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java rename to log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/RecyclerFactoriesTest.java index 1a9d23531c0..8308d45e78f 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/RecyclerFactoriesTest.java @@ -14,7 +14,11 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; +package org.apache.logging.log4j.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; @@ -23,14 +27,11 @@ import org.apache.logging.log4j.plugins.convert.TypeConverter; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.util.*; import org.assertj.core.api.Assertions; import org.jctools.queues.MpmcArrayQueue; import org.junit.jupiter.api.Test; -import java.lang.reflect.Field; -import java.util.ArrayDeque; -import java.util.concurrent.ArrayBlockingQueue; - class RecyclerFactoriesTest { @Test diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java similarity index 96% rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java rename to log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java index f2790708d92..9e17e08e126 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/StringParameterParserTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java @@ -14,15 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.layout.template.json.util; - -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.DoubleQuotedStringValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.NullValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.StringValue; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Value; -import org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; +package org.apache.logging.log4j.util; import java.util.Collections; import java.util.LinkedHashMap; @@ -30,6 +22,15 @@ import java.util.Map; import java.util.Set; +import org.apache.logging.log4j.util.StringParameterParser; +import org.apache.logging.log4j.util.StringParameterParser.DoubleQuotedStringValue; +import org.apache.logging.log4j.util.StringParameterParser.NullValue; +import org.apache.logging.log4j.util.StringParameterParser.StringValue; +import org.apache.logging.log4j.util.StringParameterParser.Value; +import org.apache.logging.log4j.util.StringParameterParser.Values; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + @SuppressWarnings("DoubleBraceInitialization") class StringParameterParserTest { diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java index 4e57726fc22..8f785db960e 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java @@ -16,6 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; @@ -28,24 +38,14 @@ import org.apache.logging.log4j.core.util.StringEncoder; import org.apache.logging.log4j.layout.template.json.resolver.*; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; import org.apache.logging.log4j.layout.template.json.util.Uris; import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Recycler; +import org.apache.logging.log4j.util.RecyclerFactory; import org.apache.logging.log4j.util.Strings; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - @Configurable(elementType = Layout.ELEMENT_TYPE) @Plugin public class JsonTemplateLayout implements StringLayout { diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java index e47acfb94a5..7f9b27df75c 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,10 +21,10 @@ 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; +import org.apache.logging.log4j.util.RecyclerFactories; +import org.apache.logging.log4j.util.RecyclerFactory; public final class JsonTemplateLayoutDefaults { diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java index aa4a1392dcb..07a85a77fbb 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java @@ -16,16 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; - import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.Recycler; + /** * Resolves a number from an internal counter. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java index d7a5793d28d..42dceef2fe5 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java @@ -16,18 +16,18 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Objects; + import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; +import org.apache.logging.log4j.util.RecyclerFactory; import org.apache.logging.log4j.util.Strings; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Objects; - /** * {@link TemplateResolverContext} specialized for {@link LogEvent}s. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java index 9b8182ea89e..07cd58e82d7 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.util.Recycler; /** * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java index 788fa5de4df..a8cf1cbd2c7 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java @@ -6,7 +6,7 @@ * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,18 +16,18 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.layout.template.json.util.Recycler; -import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.TriConsumer; - import java.util.Map; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.Recycler; +import org.apache.logging.log4j.util.RecyclerFactory; +import org.apache.logging.log4j.util.TriConsumer; + /** * {@link ReadOnlyStringMap} resolver. * diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java index 92c2d33252c..b1691bb90b7 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java @@ -16,14 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import org.apache.logging.log4j.layout.template.json.util.*; - import java.util.List; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.apache.logging.log4j.layout.template.json.util.*; +import org.apache.logging.log4j.util.Recycler; +import org.apache.logging.log4j.util.RecyclerFactory; + /** * Exception stack trace to JSON string resolver used by {@link ExceptionResolver}. */ diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java deleted file mode 100644 index 16b945c09b7..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.layout.template.json.util; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -@FunctionalInterface -public interface RecyclerFactory { - - default Recycler create(final Supplier supplier) { - return create(supplier, ignored -> {}); - } - - Recycler create(Supplier supplier, Consumer cleaner); - -} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java index ce0dc999bfc..b8c7586352a 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java @@ -19,6 +19,8 @@ import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.convert.TypeConverter; import org.apache.logging.log4j.plugins.convert.TypeConverters; +import org.apache.logging.log4j.util.RecyclerFactories; +import org.apache.logging.log4j.util.RecyclerFactory; /** * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter. 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 8edeac77044..0c8458e30b9 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 @@ -16,7 +16,10 @@ */ package org.apache.logging.log4j.layout.template.json; -import co.elastic.logging.log4j2.EcsLayout; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; + import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; @@ -27,13 +30,11 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.jackson.json.layout.JsonLayout; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; -import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecyclerFactory; +import org.apache.logging.log4j.util.ThreadLocalRecyclerFactory; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; +import co.elastic.logging.log4j2.EcsLayout; @State(Scope.Thread) public class JsonTemplateLayoutBenchmarkState { From 156e747bc4a07260daa6ddb04e5510e6db048f95 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sat, 14 Jan 2023 14:34:08 -0600 Subject: [PATCH 03/39] Move Recycler API to spi and fix related queue factory classes Signed-off-by: Matt Sicker --- .../{util => spi}/DummyRecyclerFactory.java | 24 +++- .../QueueSupplier.java} | 27 +---- .../QueueingRecyclerFactory.java | 49 +++++++- .../logging/log4j/{util => spi}/Recycler.java | 2 +- .../{util => spi}/RecyclerFactories.java | 112 +++++++++++++----- .../log4j/{util => spi}/RecyclerFactory.java | 2 +- .../ThreadLocalRecyclerFactory.java | 31 ++++- .../logging/log4j/util/QueueingRecycler.java | 61 ---------- .../log4j/util/ThreadLocalRecycler.java | 52 -------- .../log4j/core/appender/AsyncAppender.java | 24 ++-- .../core/async/ArrayBlockingQueueFactory.java | 14 +-- .../core/async/BlockingQueueFactory.java | 4 +- .../async/DisruptorBlockingQueueFactory.java | 15 +-- .../async/JCToolsBlockingQueueFactory.java | 18 +-- .../async/LinkedTransferQueueFactory.java | 15 ++- .../layout/template/json/LogstashIT.java | 2 +- .../ThreadLocalRecyclerNestedLoggingTest.java | 4 +- .../{util => spi}/RecyclerFactoriesTest.java | 17 ++- .../template/json/JsonTemplateLayout.java | 4 +- .../json/JsonTemplateLayoutDefaults.java | 4 +- .../json/resolver/CounterResolver.java | 2 +- .../json/resolver/EventResolverContext.java | 2 +- .../resolver/MessageParameterResolver.java | 2 +- .../resolver/ReadOnlyStringMapResolver.java | 4 +- .../resolver/StackTraceStringResolver.java | 4 +- .../json/util/RecyclerFactoryConverter.java | 4 +- .../JsonTemplateLayoutBenchmarkState.java | 2 +- 27 files changed, 253 insertions(+), 248 deletions(-) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/DummyRecyclerFactory.java (71%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util/DummyRecycler.java => spi/QueueSupplier.java} (60%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/QueueingRecyclerFactory.java (50%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/Recycler.java (97%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/RecyclerFactories.java (68%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/RecyclerFactory.java (98%) rename log4j-api/src/main/java/org/apache/logging/log4j/{util => spi}/ThreadLocalRecyclerFactory.java (59%) delete mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java delete mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java rename log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/{util => spi}/RecyclerFactoriesTest.java (89%) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java similarity index 71% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index 2430efecf3f..f86d2c67cbb 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -14,11 +14,16 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; import java.util.function.Consumer; import java.util.function.Supplier; +/** + * Recycler strategy which doesn't recycle anything; all instances are freshly created. + * + * @since 3.0.0 + */ public class DummyRecyclerFactory implements RecyclerFactory { private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory(); @@ -36,4 +41,21 @@ public Recycler create( return new DummyRecycler<>(supplier); } + private static class DummyRecycler implements Recycler { + + private final Supplier supplier; + + private DummyRecycler(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public V acquire() { + return supplier.get(); + } + + @Override + public void release(final V value) {} + + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java similarity index 60% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecycler.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java index b8e6699c3c9..46eb0478119 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/DummyRecycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java @@ -14,29 +14,10 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; -import java.util.function.Supplier; - -/** - * Recycler strategy which doesn't recycle anything; all instances are freshly created. - * - * @param the recyclable type - */ -public class DummyRecycler implements Recycler { - - private final Supplier supplier; - - public DummyRecycler(final Supplier supplier) { - this.supplier = supplier; - } - - @Override - public V acquire() { - return supplier.get(); - } - - @Override - public void release(final V value) {} +import java.util.Queue; +public interface QueueSupplier { + Queue create(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java similarity index 50% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java index 19b485c45b4..f021e93376f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueingRecyclerFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; import java.util.Queue; import java.util.function.Consumer; @@ -22,9 +22,9 @@ public class QueueingRecyclerFactory implements RecyclerFactory { - private final Supplier> queueSupplier; + private final QueueSupplier queueSupplier; - public QueueingRecyclerFactory(final Supplier> queueSupplier) { + public QueueingRecyclerFactory(final QueueSupplier queueSupplier) { this.queueSupplier = queueSupplier; } @@ -32,9 +32,48 @@ public QueueingRecyclerFactory(final Supplier> queueSupplier) { public Recycler create( final Supplier supplier, final Consumer cleaner) { - @SuppressWarnings("unchecked") - final Queue queue = (Queue) queueSupplier.get(); + final Queue queue = queueSupplier.create(); return new QueueingRecycler<>(supplier, cleaner, queue); } + // Visible for tests. + static class QueueingRecycler implements Recycler { + + private final Supplier supplier; + + private final Consumer cleaner; + + private final Queue queue; + + private 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-api/src/main/java/org/apache/logging/log4j/util/Recycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java similarity index 97% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/Recycler.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java index 1a41b03827d..5a291baf719 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Recycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; /** * Strategy for recycling objects. This is primarily useful for heavyweight objects and buffers. diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java similarity index 68% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java index 581367a250d..d1e5ba73c9d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactories.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java @@ -14,18 +14,22 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; - -import org.jctools.queues.MpmcArrayQueue; +package org.apache.logging.log4j.spi; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; 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.Cast; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.ReflectionUtil; +import org.apache.logging.log4j.util.StringParameterParser; +import org.jctools.queues.MpmcArrayQueue; import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; @@ -55,7 +59,73 @@ public Queue create(final int capacity) { } } - private static Supplier> getQueueSupplier(final int capacity) { + private static class ConstructedQueueFactory implements QueueFactory { + private final String queueFactorySpec; + private final Constructor constructor; + + private ConstructedQueueFactory(final String queueFactorySpec, final Constructor constructor) { + this.queueFactorySpec = queueFactorySpec; + this.constructor = constructor; + } + + @Override + public Queue create(final int capacity) { + final Constructor> typedConstructor = Cast.cast(constructor); + try { + return typedConstructor.newInstance(capacity); + } catch (InvocationTargetException e) { + throw new RuntimeException( + "recycler queue construction failed for factory: " + + queueFactorySpec, e.getCause()); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( + "recycler queue construction failed for factory: " + + queueFactorySpec, e); + } + } + } + + private static class MethodProvidedQueueFactory implements QueueFactory { + private final String queueFactorySpec; + private final Method method; + + private MethodProvidedQueueFactory(final String queueFactorySpec, final Method method) { + this.queueFactorySpec = queueFactorySpec; + this.method = method; + } + + @Override + public Queue create(final int capacity) { + try { + return Cast.cast(method.invoke(null, capacity)); + } catch (InvocationTargetException e) { + throw new RuntimeException( + "recycler queue construction failed for factory: " + + queueFactorySpec, e.getCause()); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "recycler queue construction failed for factory: " + + queueFactorySpec, e); + } + } + } + + private static class QueueFactorySupplier implements QueueSupplier { + private final QueueFactory queueFactory; + private final int capacity; + + private QueueFactorySupplier(final QueueFactory queueFactory, final int capacity) { + this.queueFactory = queueFactory; + this.capacity = capacity; + } + + @Override + public Queue create() { + return queueFactory.create(capacity); + } + } + + private static QueueSupplier getQueueSupplier(final int capacity) { final ClassLoader classLoader = RecyclerFactories.class.getClassLoader(); Class factoryClass; try { @@ -66,7 +136,7 @@ private static Supplier> getQueueSupplier(final int capacity) { factoryClass = ArrayBlockingQueueFactory.class; } final QueueFactory queueFactory = ReflectionUtil.instantiate(factoryClass); - return () -> queueFactory.create(capacity); + return new QueueFactorySupplier(queueFactory, capacity); } public static RecyclerFactory ofSpec(final String recyclerFactorySpec) { @@ -169,39 +239,17 @@ private static RecyclerFactory createRecyclerFactory( final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1); try { final Class supplierClass = LoaderUtil.loadClass(supplierClassName); - final Supplier> queueSupplier; + final QueueFactory queueFactory; 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); - } - }; + queueFactory = new ConstructedQueueFactory(queueFactorySpec, supplierCtor); } 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); - } - }; + queueFactory = new MethodProvidedQueueFactory(queueFactorySpec, supplierMethod); } - return new QueueingRecyclerFactory(queueSupplier); + return new QueueingRecyclerFactory(new QueueFactorySupplier(queueFactory, capacity)); } catch (final Exception error) { throw new RuntimeException( "failed executing queueing recycler factory: " + diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java similarity index 98% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java index eab78a702d3..90b81327556 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/RecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; import java.util.function.Consumer; import java.util.function.Supplier; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java similarity index 59% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java index c877b4d24b7..fe2ecdfa001 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java @@ -14,11 +14,16 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; import java.util.function.Consumer; import java.util.function.Supplier; +/** + * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy + * may not be appropriate in workloads where units of work are independent of operating system threads such as + * reactive streams, coroutines, or virtual threads. + */ public class ThreadLocalRecyclerFactory implements RecyclerFactory { private static final ThreadLocalRecyclerFactory INSTANCE = @@ -37,4 +42,28 @@ public Recycler create( return new ThreadLocalRecycler<>(supplier, cleaner); } + private static class ThreadLocalRecycler implements Recycler { + + private final Consumer cleaner; + + private final ThreadLocal holder; + + private 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-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueingRecycler.java deleted file mode 100644 index e6a422f7337..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/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.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-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java deleted file mode 100644 index 98957bffde6..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ThreadLocalRecycler.java +++ /dev/null @@ -1,52 +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.util; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy - * may not be appropriate in workloads where units of work are independent of operating system threads such as - * reactive streams, coroutines, or virtual threads. - * - * @param the recyclable type - */ -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-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 6a3c9c9d0a8..2dda1f9e2ed 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 @@ -16,6 +16,13 @@ */ package org.apache.logging.log4j.core.appender; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TransferQueue; + import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; @@ -44,13 +51,6 @@ import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.spi.AbstractLogger; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TransferQueue; - /** * Appends to one or more Appenders asynchronously. You can configure an AsyncAppender with one or more Appenders and an * Appender to append to if the queue is full. The AsyncAppender does not allow a filter to be specified on the Appender @@ -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; @@ -271,7 +271,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; @@ -318,7 +318,7 @@ public Builder setIgnoreExceptions(final boolean ignoreExceptions) { return this; } - public Builder setBlockingQueueFactory(final BlockingQueueFactory blockingQueueFactory) { + public Builder setBlockingQueueFactory(final BlockingQueueFactory blockingQueueFactory) { this.blockingQueueFactory = blockingQueueFactory; return this; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java index 3126a65e76e..ed495dd55c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java @@ -16,13 +16,13 @@ */ package org.apache.logging.log4j.core.async; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginFactory; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; - /** * Factory for creating instances of {@link ArrayBlockingQueue}. * @@ -30,14 +30,14 @@ */ @Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE) @Plugin("ArrayBlockingQueue") -public class ArrayBlockingQueueFactory implements BlockingQueueFactory { +public class ArrayBlockingQueueFactory implements BlockingQueueFactory { @Override - public BlockingQueue create(final int capacity) { + public BlockingQueue create(final int capacity) { return new ArrayBlockingQueue<>(capacity); } @PluginFactory - public static ArrayBlockingQueueFactory createFactory() { - return new ArrayBlockingQueueFactory<>(); + public static ArrayBlockingQueueFactory createFactory() { + return new ArrayBlockingQueueFactory(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java index 8f0e7b745e2..2ae6a27ae38 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java @@ -23,7 +23,7 @@ * * @since 2.7 */ -public interface BlockingQueueFactory { +public interface BlockingQueueFactory { /** * The {@link org.apache.logging.log4j.plugins.Configurable#elementType() element type} to use for plugins @@ -38,5 +38,5 @@ public interface BlockingQueueFactory { * @param capacity maximum size of the queue if supported * @return a new BlockingQueue */ - BlockingQueue create(int capacity); + BlockingQueue create(int capacity); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java index 39bc3ccda3c..69fb710609c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java @@ -16,14 +16,15 @@ */ package org.apache.logging.log4j.core.async; -import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; -import com.conversantmedia.util.concurrent.SpinPolicy; +import java.util.concurrent.BlockingQueue; + import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginAttribute; import org.apache.logging.log4j.plugins.PluginFactory; -import java.util.concurrent.BlockingQueue; +import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; +import com.conversantmedia.util.concurrent.SpinPolicy; /** * Factory for creating instances of {@link DisruptorBlockingQueue}. @@ -32,7 +33,7 @@ */ @Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) @Plugin("DisruptorBlockingQueue") -public class DisruptorBlockingQueueFactory implements BlockingQueueFactory { +public class DisruptorBlockingQueueFactory implements BlockingQueueFactory { private final SpinPolicy spinPolicy; @@ -41,14 +42,14 @@ private DisruptorBlockingQueueFactory(final SpinPolicy spinPolicy) { } @Override - public BlockingQueue create(final int capacity) { + public BlockingQueue create(final int capacity) { return new DisruptorBlockingQueue<>(capacity, spinPolicy); } @PluginFactory - public static DisruptorBlockingQueueFactory createFactory( + public static DisruptorBlockingQueueFactory createFactory( @PluginAttribute(defaultString = "WAITING") final SpinPolicy spinPolicy ) { - return new DisruptorBlockingQueueFactory<>(spinPolicy); + return new DisruptorBlockingQueueFactory(spinPolicy); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java index 1e304dd8a58..c6df4e02e85 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java @@ -16,17 +16,17 @@ */ package org.apache.logging.log4j.core.async; +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginAttribute; import org.apache.logging.log4j.plugins.PluginFactory; import org.jctools.queues.MpscArrayQueue; -import java.util.Collection; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - /** * Factory for creating instances of BlockingQueues backed by JCTools {@link MpscArrayQueue}. * @@ -34,7 +34,7 @@ */ @Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) @Plugin("JCToolsBlockingQueue") -public class JCToolsBlockingQueueFactory implements BlockingQueueFactory { +public class JCToolsBlockingQueueFactory implements BlockingQueueFactory { private final WaitStrategy waitStrategy; @@ -43,14 +43,14 @@ private JCToolsBlockingQueueFactory(final WaitStrategy waitStrategy) { } @Override - public BlockingQueue create(final int capacity) { + public BlockingQueue create(final int capacity) { return new MpscBlockingQueue<>(capacity, waitStrategy); } @PluginFactory - public static JCToolsBlockingQueueFactory createFactory( + public static JCToolsBlockingQueueFactory createFactory( @PluginAttribute(defaultString = "PARK") final WaitStrategy waitStrategy) { - return new JCToolsBlockingQueueFactory<>(waitStrategy); + return new JCToolsBlockingQueueFactory(waitStrategy); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java index d5dfa11e88e..46f4b3e8951 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java @@ -14,16 +14,15 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.async; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedTransferQueue; + import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginFactory; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedTransferQueue; - /** * Factory for creating instances of {@link LinkedTransferQueue}. * @@ -31,14 +30,14 @@ */ @Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) @Plugin("LinkedTransferQueue") -public class LinkedTransferQueueFactory implements BlockingQueueFactory { +public class LinkedTransferQueueFactory implements BlockingQueueFactory { @Override - public BlockingQueue create(final int capacity) { + public BlockingQueue create(final int capacity) { return new LinkedTransferQueue<>(); } @PluginFactory - public static LinkedTransferQueueFactory createFactory() { - return new LinkedTransferQueueFactory<>(); + public static LinkedTransferQueueFactory createFactory() { + return new LinkedTransferQueueFactory(); } } diff --git a/log4j-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 b657e5bae57..49090b03147 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,8 +45,8 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.ThreadLocalRecyclerFactory; import org.assertj.core.api.Assertions; import org.awaitility.Awaitility; import org.elasticsearch.ElasticsearchStatusException; 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 1dfc7acd719..639fc0887f4 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/ThreadLocalRecyclerNestedLoggingTest.java @@ -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.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} 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/util/RecyclerFactoriesTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java similarity index 89% rename from log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/RecyclerFactoriesTest.java rename to log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java index 8308d45e78f..74771534aaa 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/RecyclerFactoriesTest.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util; +package org.apache.logging.log4j.spi; import java.lang.reflect.Field; import java.util.ArrayDeque; @@ -27,7 +27,6 @@ import org.apache.logging.log4j.plugins.convert.TypeConverter; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.di.Injector; -import org.apache.logging.log4j.util.*; import org.assertj.core.api.Assertions; import org.jctools.queues.MpmcArrayQueue; import org.junit.jupiter.api.Test; @@ -80,9 +79,9 @@ void test_RecyclerFactoryConverter() throws Exception { queueingRecyclerFactory.create(Object::new); Assertions .assertThat(recycler) - .isInstanceOf(QueueingRecycler.class); - final QueueingRecycler queueingRecycler = - (QueueingRecycler) recycler; + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; Assertions .assertThat(queueingRecycler.getQueue()) .isInstanceOf(ArrayDeque.class); @@ -112,9 +111,9 @@ void test_RecyclerFactoryConverter() throws Exception { queueingRecyclerFactory.create(Object::new); Assertions .assertThat(recycler) - .isInstanceOf(QueueingRecycler.class); - final QueueingRecycler queueingRecycler = - (QueueingRecycler) recycler; + .isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class); + final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = + (QueueingRecyclerFactory.QueueingRecycler) recycler; Assertions .assertThat(queueingRecycler.getQueue()) .isInstanceOf(ArrayBlockingQueue.class); @@ -133,7 +132,7 @@ void test_RecyclerFactoryConverter_using_XML_config( final JsonTemplateLayout layout = (JsonTemplateLayout) appender.getLayout(); final Field field = JsonTemplateLayout.class.getDeclaredField("contextRecycler"); field.setAccessible(true); - final QueueingRecycler contextRecycler = (QueueingRecycler) field.get(layout); + final QueueingRecyclerFactory.QueueingRecycler contextRecycler = (QueueingRecyclerFactory.QueueingRecycler) field.get(layout); final MpmcArrayQueue queue = (MpmcArrayQueue) contextRecycler.getQueue(); Assertions.assertThat(queue.capacity()).isEqualTo(512); } diff --git a/log4j-layout-template-json/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 8f785db960e..1bd4895de16 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java @@ -41,9 +41,9 @@ import org.apache.logging.log4j.layout.template.json.util.Uris; import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Recycler; -import org.apache.logging.log4j.util.RecyclerFactory; import org.apache.logging.log4j.util.Strings; @Configurable(elementType = Layout.ELEMENT_TYPE) 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 7f9b27df75c..db77071681c 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutDefaults.java @@ -21,10 +21,10 @@ import java.util.Locale; import java.util.TimeZone; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; -import org.apache.logging.log4j.util.RecyclerFactories; -import org.apache.logging.log4j.util.RecyclerFactory; public final class JsonTemplateLayoutDefaults { 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 07a85a77fbb..139a699f19f 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java @@ -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.util.Recycler; +import org.apache.logging.log4j.spi.Recycler; /** * Resolves a number from an internal counter. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java index 42dceef2fe5..28038ae81f9 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java @@ -25,7 +25,7 @@ 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.util.RecyclerFactory; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.Strings; /** 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 07cd58e82d7..3dc80ad16a8 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java @@ -21,7 +21,7 @@ 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.util.Recycler; +import org.apache.logging.log4j.spi.Recycler; /** * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java index a8cf1cbd2c7..7ea4730186f 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java @@ -23,9 +23,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.Recycler; -import org.apache.logging.log4j.util.RecyclerFactory; import org.apache.logging.log4j.util.TriConsumer; /** 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 b1691bb90b7..35c2f23ca12 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java @@ -23,8 +23,8 @@ import java.util.stream.Collectors; import org.apache.logging.log4j.layout.template.json.util.*; -import org.apache.logging.log4j.util.Recycler; -import org.apache.logging.log4j.util.RecyclerFactory; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; /** * Exception stack trace to JSON string resolver used by {@link ExceptionResolver}. diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java index b8c7586352a..4d596872085 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java @@ -19,8 +19,8 @@ import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.convert.TypeConverter; import org.apache.logging.log4j.plugins.convert.TypeConverters; -import org.apache.logging.log4j.util.RecyclerFactories; -import org.apache.logging.log4j.util.RecyclerFactory; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; /** * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter. diff --git a/log4j-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 0c8458e30b9..0804967013d 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.jackson.json.layout.JsonLayout; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; -import org.apache.logging.log4j.util.ThreadLocalRecyclerFactory; +import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; From b0018311dc5e17dec49ba716fa66ab4beafc3be9 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sat, 14 Jan 2023 16:15:06 -0600 Subject: [PATCH 04/39] Update ReusableMessageFactory to use Recycler API Also updates ThreadLocalRecyclerFactory to support nested calls to acquire() by using an ArrayDeque to track potentially more than one object. Signed-off-by: Matt Sicker --- .../message/ReusableMessageFactoryTest.java | 32 ++++--- .../spi/ThreadLocalRecyclerFactoryTest.java | 51 +++++++++++ .../logging/log4j/message/MessageFactory.java | 13 +++ .../log4j/message/ReusableMessageFactory.java | 88 +++++++++---------- .../message/ReusableParameterizedMessage.java | 46 ++++------ .../logging/log4j/spi/AbstractLogger.java | 4 +- .../log4j/spi/ThreadLocalRecyclerFactory.java | 28 ++++-- .../core/async/RingBufferLogEventTest.java | 4 +- .../jackson/json/layout/JsonLayoutTest.java | 10 ++- 9 files changed, 177 insertions(+), 99 deletions(-) create mode 100644 log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java index f5e293134e7..6ac576f7a88 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.message; +import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -25,35 +27,39 @@ */ public class ReusableMessageFactoryTest { + private ReusableMessageFactory factory; + + @BeforeEach + void setUp() { + factory = new ReusableMessageFactory(ThreadLocalRecyclerFactory.getInstance()); + } + @Test public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); assertNotSame(message1, message2); - ReusableMessageFactory.release(message1); - ReusableMessageFactory.release(message2); + factory.recycle(message1); + factory.recycle(message2); } @Test public void testCreateEventReturnsSameInstance() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); - ReusableMessageFactory.release(message1); + factory.recycle(message1); final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); assertSame(message1, message2); - ReusableMessageFactory.release(message2); + factory.recycle(message2); final Message message3 = factory.newMessage("text, AAA={} BBB={} p2={} p3={}", 9, 8, 7, 6); assertSame(message2, message3); - ReusableMessageFactory.release(message3); + factory.recycle(message3); } private void assertReusableParameterizeMessage(final Message message, final String txt, final Object[] params) { assertTrue(message instanceof ReusableParameterizedMessage); final ReusableParameterizedMessage msg = (ReusableParameterizedMessage) message; - assertTrue(msg.reserved, "reserved"); assertEquals(txt, msg.getFormat()); assertEquals(msg.getParameterCount(), params.length, "count"); @@ -65,7 +71,6 @@ private void assertReusableParameterizeMessage(final Message message, final Stri @Test public void testCreateEventOverwritesFields() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); assertReusableParameterizeMessage(message1, "text, p0={} p1={} p2={} p3={}", new Object[]{ new Integer(1), // @@ -74,7 +79,7 @@ public void testCreateEventOverwritesFields() throws Exception { new Integer(4), // }); - ReusableMessageFactory.release(message1); + factory.recycle(message1); final Message message2 = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4); assertReusableParameterizeMessage(message1, "other, A={} B={} C={} D={}", new Object[]{ new Integer(1), // @@ -83,12 +88,11 @@ public void testCreateEventOverwritesFields() throws Exception { new Integer(4), // }); assertSame(message1, message2); - ReusableMessageFactory.release(message2); + factory.recycle(message2); } @Test public void testCreateEventReturnsThreadLocalInstance() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); final Message[] message1 = new Message[1]; final Message[] message2 = new Message[1]; final Thread t1 = new Thread("THREAD 1") { @@ -123,8 +127,8 @@ public void run() { new Integer(3), // new Integer(4), // }); - ReusableMessageFactory.release(message1[0]); - ReusableMessageFactory.release(message2[0]); + factory.recycle(message1[0]); + factory.recycle(message2[0]); } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java new file mode 100644 index 00000000000..2f159d17682 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactoryTest.java @@ -0,0 +1,51 @@ +/* + * 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.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ThreadLocalRecyclerFactoryTest { + @Test + void nestedAcquiresDoNotInterfere() { + final Recycler r = ThreadLocalRecyclerFactory.getInstance() + .create(AtomicInteger::new, i -> i.set(0)); + final var recycler = (ThreadLocalRecyclerFactory.ThreadLocalRecycler) r; + + assertThat(recycler.getQueue()).isEmpty(); + final AtomicInteger first = recycler.acquire(); + assertThat(recycler.getQueue()).isEmpty(); + final AtomicInteger second = recycler.acquire(); + assertThat(recycler.getQueue()).isEmpty(); + first.set(1); + second.set(2); + final AtomicInteger third = recycler.acquire(); + assertThat(recycler.getQueue()).isEmpty(); + assertThat(third.get()).isEqualTo(0); + assertThat(first.get()).isEqualTo(1); + assertThat(second.get()).isEqualTo(2); + recycler.release(first); + assertThat(recycler.getQueue()).hasSize(1); + recycler.release(second); + assertThat(recycler.getQueue()).hasSize(2); + recycler.release(third); + assertThat(recycler.getQueue()).hasSize(3); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java index abe07ba4bc4..50028f92c48 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 @@ -236,4 +236,17 @@ default Message newMessage(final String message, final Object p0, final Object p final Object p6, final Object p7, final Object p8, final Object p9) { return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 }); } + + /** + * Recycles a message back for potential reuse or cleanup. + * + * @since 3.0.0 + * @see org.apache.logging.log4j.spi.Recycler + */ + default void recycle(Message message) { + // by default, we'll delegate to the old API for compatibility + if (message instanceof Clearable) { + ((Clearable) message).clear(); + } + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index d7d7b8cf564..9461b3c5485 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 @@ -18,6 +18,9 @@ import java.io.Serializable; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -27,6 +30,7 @@ * @see ReusableSimpleMessage * @see ReusableObjectMessage * @see ReusableParameterizedMessage + * @see Recycler * @since 2.6 */ @PerformanceSensitive("allocation") @@ -38,42 +42,26 @@ public final class ReusableMessageFactory implements MessageFactory, Serializabl public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory(); private static final long serialVersionUID = -8970940216592525651L; - private static final ThreadLocal threadLocalParameterized = new ThreadLocal<>(); - private static final ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>(); - private static final ThreadLocal threadLocalObjectMessage = new ThreadLocal<>(); + + private final Recycler parameterizedMessageRecycler; + private final Recycler simpleMessageRecycler; + private final Recycler objectMessageRecycler; /** - * Constructs a message factory. + * Constructs a message factory using the default {@link RecyclerFactory}. */ public ReusableMessageFactory() { - super(); - } - - private static ReusableParameterizedMessage getParameterized() { - ReusableParameterizedMessage result = threadLocalParameterized.get(); - if (result == null) { - result = new ReusableParameterizedMessage(); - threadLocalParameterized.set(result); - } - return result.reserved ? new ReusableParameterizedMessage().reserve() : result.reserve(); - } - - private static ReusableSimpleMessage getSimple() { - ReusableSimpleMessage result = threadLocalSimpleMessage.get(); - if (result == null) { - result = new ReusableSimpleMessage(); - threadLocalSimpleMessage.set(result); - } - return result; + this(RecyclerFactories.ofSpec(null)); } - private static ReusableObjectMessage getObject() { - ReusableObjectMessage result = threadLocalObjectMessage.get(); - if (result == null) { - result = new ReusableObjectMessage(); - threadLocalObjectMessage.set(result); - } - return result; + public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { + super(); + parameterizedMessageRecycler = recyclerFactory + .create(ReusableParameterizedMessage::new, ReusableParameterizedMessage::clear); + simpleMessageRecycler = recyclerFactory + .create(ReusableSimpleMessage::new, ReusableSimpleMessage::clear); + objectMessageRecycler = recyclerFactory + .create(ReusableObjectMessage::new, ReusableObjectMessage::clear); } /** @@ -89,9 +77,21 @@ public static void release(final Message message) { // LOG4J2-1583 } } + @Override + public void recycle(final Message message) { + // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today! + if (message instanceof ReusableParameterizedMessage) { + parameterizedMessageRecycler.release((ReusableParameterizedMessage) message); + } else if (message instanceof ReusableObjectMessage) { + objectMessageRecycler.release((ReusableObjectMessage) message); + } else if (message instanceof ReusableSimpleMessage) { + simpleMessageRecycler.release((ReusableSimpleMessage) message); + } + } + @Override public Message newMessage(final CharSequence charSequence) { - final ReusableSimpleMessage result = getSimple(); + final ReusableSimpleMessage result = simpleMessageRecycler.acquire(); result.set(charSequence); return result; } @@ -107,64 +107,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 +177,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 +193,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 be3db8f9a6d..47570bcc794 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.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactories; +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; @@ -35,7 +38,6 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV private static final int MIN_BUILDER_SIZE = 512; private static final int MAX_PARMS = 10; private static final long serialVersionUID = 7800075879295123856L; - private transient ThreadLocal buffer; // non-static: LOG4J2-1583 private String messagePattern; private int argCount; @@ -44,12 +46,24 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV private transient Object[] varargs; private transient Object[] params = new Object[MAX_PARMS]; private transient Throwable throwable; - transient boolean reserved = false; // LOG4J2-1583 prevent scrambled logs with nested logging calls + + private final Recycler bufferRecycler; // non-static: LOG4J2-1583 /** * Creates a reusable message. */ public ReusableParameterizedMessage() { + this(RecyclerFactories.ofSpec(null)); + } + + public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { + bufferRecycler = recyclerFactory.create( + () -> { + final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length(); + return new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2)); + }, + buffer -> buffer.setLength(0) + ); } private Object[] getTrimmedParams() { @@ -300,24 +314,11 @@ public Throwable getThrowable() { */ @Override public String getFormattedMessage() { - final StringBuilder sb = getBuffer(); + final StringBuilder sb = bufferRecycler.acquire(); 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); - } - result.setLength(0); + bufferRecycler.release(sb); return result; } @@ -330,16 +331,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=" + @@ -350,7 +341,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 3760897b76c..51991a85487 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 @@ -1950,7 +1950,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); } } @@ -1984,7 +1984,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); } } 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 index fe2ecdfa001..f04ba060485 100644 --- 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 @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.spi; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; @@ -42,28 +45,43 @@ public Recycler create( return new ThreadLocalRecycler<>(supplier, cleaner); } - private static class ThreadLocalRecycler implements Recycler { + // Visible for testing + static class ThreadLocalRecycler implements Recycler { + + private final Supplier supplier; private final Consumer cleaner; - private final ThreadLocal holder; + private final ThreadLocal> holder; private ThreadLocalRecycler( final Supplier supplier, final Consumer cleaner) { + this.supplier = supplier; this.cleaner = cleaner; - this.holder = ThreadLocal.withInitial(supplier); + this.holder = ThreadLocal.withInitial(ArrayDeque::new); } @Override public V acquire() { - final V value = holder.get(); + final Queue queue = holder.get(); + final V value = queue.poll(); + if (value == null) { + return supplier.get(); + } cleaner.accept(value); return value; } @Override - public void release(final V value) {} + public void release(final V value) { + holder.get().offer(value); + } + + // Visible for testing + Queue getQueue() { + return holder.get(); + } } } 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 2971d1ebb43..a76f076d6e3 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 @@ -233,7 +233,7 @@ public void testCreateMementoRetainsParametersAndFormat() { assertArrayEquals(new String[]{"World"}, actual.getParameters()); assertEquals("Hello World!", actual.getFormattedMessage()); } finally { - ReusableMessageFactory.release(message); + factory.recycle(message); } } @@ -261,7 +261,7 @@ public void testMementoReuse() { final Message memento2 = evt.memento(); assertThat(memento1, sameInstance(memento2)); } finally { - ReusableMessageFactory.release(message); + factory.recycle(message); } } diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java index 7eacd23781a..0285a828039 100644 --- a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java @@ -521,7 +521,8 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception { .setCharset(StandardCharsets.UTF_8) .setIncludeStacktrace(true) .build(); - Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE; + Message message = factory.newMessage("Testing {}", new TestObj()); try { final Log4jLogEvent expected = Log4jLogEvent.newBuilder() .setLoggerName("a.B") @@ -538,7 +539,7 @@ public void testReusableLayoutMessageWithCurlyBraces() throws Exception { final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); assertEquals(expectedMessage, actual.getMessage().getFormattedMessage()); } finally { - ReusableMessageFactory.release(message); + factory.recycle(message); } } @@ -556,7 +557,8 @@ public void testLayoutRingBufferEventReusableMessageWithCurlyBraces() throws Exc .setCharset(StandardCharsets.UTF_8) .setIncludeStacktrace(true) .build(); - Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + final ReusableMessageFactory factory = ReusableMessageFactory.INSTANCE; + Message message = factory.newMessage("Testing {}", new TestObj()); try { RingBufferLogEvent ringBufferEvent = new RingBufferLogEvent(); ringBufferEvent.setValues( @@ -569,7 +571,7 @@ null, new SortedArrayStringMap(), ThreadContext.EMPTY_STACK, 1L, final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); assertEquals(expectedMessage, actual.getMessage().getFormattedMessage()); } finally { - ReusableMessageFactory.release(message); + factory.recycle(message); } } From ade318414d775fb8399ed794a02465cbe0c8a0f4 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 15 Jan 2023 14:40:02 -0600 Subject: [PATCH 05/39] Extract ReusableLogEvent interface and use Recycler API Signed-off-by: Matt Sicker --- .../logging/log4j/message/MessageFactory.java | 7 +- .../log4j/message/ReusableMessageFactory.java | 25 +++--- .../log4j/core/GarbageCollectionHelper.java | 31 +++---- .../impl/ReusableLogEventFactoryTest.java | 48 +++++------ .../logging/log4j/core/ReusableLogEvent.java | 71 ++++++++++++++++ .../logging/log4j/core/async/AsyncLogger.java | 15 ++-- .../async/AsyncLoggerConfigDisruptor.java | 5 +- .../log4j/core/async/RingBufferLogEvent.java | 83 +++++++++++++++++-- .../log4j/core/config/LoggerConfig.java | 8 +- .../log4j/core/impl/DefaultBundle.java | 11 ++- .../log4j/core/impl/Log4jLogEvent.java | 9 +- .../log4j/core/impl/LogEventFactory.java | 12 ++- .../log4j/core/impl/MutableLogEvent.java | 42 ++++++---- .../core/impl/ReusableLogEventFactory.java | 60 +++++++------- 14 files changed, 294 insertions(+), 133 deletions(-) create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java 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 50028f92c48..142d63b1d36 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java @@ -18,6 +18,8 @@ /** * Creates messages. Implementations can provide different message format syntaxes. + * If messages created by an implementation are reusable, then the {@link #recycle(Message)} method must + * be implemented and consistently used for returning objects to be reused. * * @see ParameterizedMessageFactory * @see StringFormatterMessageFactory @@ -244,9 +246,8 @@ default Message newMessage(final String message, final Object p0, final Object p * @see org.apache.logging.log4j.spi.Recycler */ default void recycle(Message message) { - // by default, we'll delegate to the old API for compatibility - if (message instanceof Clearable) { - ((Clearable) message).clear(); + if (message instanceof ReusableMessage) { + ((ReusableMessage) message).clear(); } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index f93dc2fa96b..e8c2794f7d4 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 @@ -24,6 +24,7 @@ /** * 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 @@ -52,12 +53,9 @@ public ReusableMessageFactory() { 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); + parameterizedMessageRecycler = recyclerFactory.create(ReusableParameterizedMessage::new); + simpleMessageRecycler = recyclerFactory.create(ReusableSimpleMessage::new); + objectMessageRecycler = recyclerFactory.create(ReusableObjectMessage::new); } /** @@ -79,12 +77,15 @@ public static void release(final Message message) { // LOG4J2-1583 @Override public void recycle(final Message message) { // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today! - if (message instanceof ReusableParameterizedMessage) { - parameterizedMessageRecycler.release((ReusableParameterizedMessage) message); - } else if (message instanceof ReusableObjectMessage) { - objectMessageRecycler.release((ReusableObjectMessage) message); - } else if (message instanceof ReusableSimpleMessage) { - simpleMessageRecycler.release((ReusableSimpleMessage) message); + if (message instanceof ReusableMessage) { + ((ReusableMessage) message).clear(); + 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); + } } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java index afb38179f50..8ed89f3f697 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core; -import com.google.common.io.ByteStreams; - import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; @@ -28,26 +26,23 @@ import static org.junit.Assert.assertTrue; public final class GarbageCollectionHelper implements Closeable, Runnable { - private static final OutputStream sink = ByteStreams.nullOutputStream(); + private static final OutputStream sink = OutputStream.nullOutputStream(); private final AtomicBoolean running = new AtomicBoolean(); private final CountDownLatch latch = new CountDownLatch(1); - private final Thread gcThread = new Thread(new Runnable() { - @Override - public void run() { - try { - while (running.get()) { - // Allocate data to help suggest a GC - try { - // 1mb of heap - sink.write(new byte[1024 * 1024]); - } catch (IOException ignored) { - } - // May no-op depending on the jvm configuration - System.gc(); + private final Thread gcThread = new Thread(() -> { + try { + while (running.get()) { + // Allocate data to help suggest a GC + try { + // 1mb of heap + sink.write(new byte[1024 * 1024]); + } catch (IOException ignored) { } - } finally { - latch.countDown(); + // May no-op depending on the jvm configuration + System.gc(); } + } finally { + latch.countDown(); } }); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java index ad44828a541..4769cf652a5 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java @@ -33,46 +33,45 @@ public class ReusableLogEventFactoryTest { private final Injector injector = DI.createInjector(); + private ReusableLogEventFactory factory; @BeforeEach void setUp() { injector.init(); + factory = injector.getInstance(ReusableLogEventFactory.class); } @Test public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { - final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); - final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); - final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null); + final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null); assertNotSame(event1, event2); - ReusableLogEventFactory.release(event1); - ReusableLogEventFactory.release(event2); + factory.recycle(event1); + factory.recycle(event2); } @Test public void testCreateEventReturnsSameInstance() throws Exception { - final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); - final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); - ReusableLogEventFactory.release(event1); - final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null); + factory.recycle(event1); + final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null); assertSame(event1, event2); - ReusableLogEventFactory.release(event2); - final LogEvent event3 = callCreateEvent(factory, "c", Level.INFO, new SimpleMessage("123"), null); + factory.recycle(event2); + final LogEvent event3 = callCreateEvent("c", Level.INFO, new SimpleMessage("123"), null); assertSame(event2, event3); - ReusableLogEventFactory.release(event3); + factory.recycle(event3); } @Test public void testCreateEventOverwritesFields() throws Exception { - final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); - final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + final LogEvent event1 = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null); assertEquals("a", event1.getLoggerName(), "logger"); assertEquals(Level.DEBUG, event1.getLevel(), "level"); assertEquals(new SimpleMessage("abc"), event1.getMessage(), "msg"); - ReusableLogEventFactory.release(event1); - final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + factory.recycle(event1); + final LogEvent event2 = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null); assertSame(event1, event2); assertEquals("b", event1.getLoggerName(), "logger"); @@ -83,26 +82,24 @@ public void testCreateEventOverwritesFields() throws Exception { assertEquals(new SimpleMessage("xyz"), event2.getMessage(), "msg"); } - private LogEvent callCreateEvent(final ReusableLogEventFactory factory, final String logger, final Level level, - final Message message, final Throwable thrown) { + private LogEvent callCreateEvent(final String logger, final Level level, final Message message, final Throwable thrown) { return factory.createEvent(logger, null, getClass().getName(), level, message, null, thrown); } @Test public void testCreateEventReturnsThreadLocalInstance() throws Exception { - final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); final LogEvent[] event1 = new LogEvent[1]; final LogEvent[] event2 = new LogEvent[1]; final Thread t1 = new Thread("THREAD 1") { @Override public void run() { - event1[0] = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + event1[0] = callCreateEvent("a", Level.DEBUG, new SimpleMessage("abc"), null); } }; final Thread t2 = new Thread("Thread 2") { @Override public void run() { - event2[0] = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + event2[0] = callCreateEvent("b", Level.INFO, new SimpleMessage("xyz"), null); } }; t1.start(); @@ -123,19 +120,18 @@ public void run() { assertEquals(new SimpleMessage("xyz"), event2[0].getMessage(), "msg"); assertEquals("Thread 2", event2[0].getThreadName(), "thread name"); assertEquals(t2.getId(), event2[0].getThreadId(), "tid"); - ReusableLogEventFactory.release(event1[0]); - ReusableLogEventFactory.release(event2[0]); + factory.recycle(event1[0]); + factory.recycle(event2[0]); } @Test public void testCreateEventInitFieldsProperly() throws Exception { - final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); - final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null); + final LogEvent event = callCreateEvent("logger", Level.INFO, new SimpleMessage("xyz"), null); try { assertNotNull(event.getContextData()); assertNotNull(event.getContextStack()); } finally { - ReusableLogEventFactory.release(event); + factory.recycle(event); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java new file mode 100644 index 00000000000..d2209164c95 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ReusableLogEvent.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.StringMap; + +public interface ReusableLogEvent extends LogEvent { + /** + * Clears all references this event has to other objects. + */ + void clear(); + + void setLoggerFqcn(final String loggerFqcn); + + void setMarker(final Marker marker); + + void setLevel(final Level level); + + void setLoggerName(final String loggerName); + + void setMessage(final Message message); + + void setThrown(final Throwable thrown); + + void setTimeMillis(final long timeMillis); + + void setInstant(final Instant instant); + + void setSource(final StackTraceElement source); + + @Override + StringMap getContextData(); + + void setContextData(final StringMap mutableContextData); + + void setContextStack(final ThreadContext.ContextStack contextStack); + + void setThreadId(final long threadId); + + void setThreadName(final String threadName); + + void setThreadPriority(final int threadPriority); + + void setNanoTime(final long nanoTime); + + /** + * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code ReusableLogEvent}. + * @param builder the builder whose fields to populate + */ + void initializeBuilder(final Log4jLogEvent.Builder builder); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java index e314e6cf9c5..1796931add0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringMap; @@ -418,7 +419,7 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin event.setValues(asyncLogger, asyncLogger.getName(), marker, fqcn, level, message, thrown, // config properties are taken care of in the EventHandler thread // in the AsyncLogger#actualAsyncLog method - contextDataInjector.injectContextData(null, (StringMap) event.getContextData()), + contextDataInjector.injectContextData(null, event.getContextData()), contextStack, currentThread.getId(), threadName, currentThread.getPriority(), location, clock, nanoClock); } @@ -492,24 +493,24 @@ public void actualAsyncLog(final RingBufferLogEvent event) { privateConfigLoggerConfig.getReliabilityStrategy().log(this, event); } - @SuppressWarnings("ForLoopReplaceableByForEach") // Avoid iterator allocation + @PerformanceSensitive("allocation") private void onPropertiesPresent(final RingBufferLogEvent event, final List properties) { final StringMap contextData = getContextData(event); - for (int i = 0, size = properties.size(); i < size; i++) { - final Property prop = properties.get(i); + // List::forEach is garbage-free when using an ArrayList or Arrays.asList + properties.forEach(prop -> { if (contextData.getValue(prop.getName()) != null) { - continue; // contextMap overrides config properties + return; // contextMap overrides config properties } final String value = prop.isValueNeedsLookup() // ? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) // : prop.getValue(); contextData.putValue(prop.getName(), value); - } + }); event.setContextData(contextData); } private static StringMap getContextData(final RingBufferLogEvent event) { - final StringMap contextData = (StringMap) event.getContextData(); + final StringMap contextData = event.getContextData(); if (contextData.isFrozen()) { final StringMap temp = ContextDataFactory.createContextData(); temp.putAll(contextData); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java index b689de0d4cf..337c27d8e3a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java @@ -22,6 +22,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.impl.LogEventFactory; @@ -80,8 +81,8 @@ public Log4jEventWrapper(final MutableLogEvent mutableLogEvent) { */ public void clear() { loggerConfig = null; - if (event instanceof MutableLogEvent) { - ((MutableLogEvent) event).clear(); + if (event instanceof ReusableLogEvent) { + ((ReusableLogEvent) event).clear(); } else { event = null; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java index 7904daaa0c3..02813f0234e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java @@ -22,6 +22,7 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext.ContextStack; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.MementoMessage; @@ -37,7 +38,6 @@ import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.TimestampMessage; -import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.Strings; @@ -50,7 +50,7 @@ * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during * the life of the RingBuffer. */ -public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence, ParameterVisitable { +public class RingBufferLogEvent implements ReusableLogEvent, ReusableMessage, CharSequence, ParameterVisitable { /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */ public static final EventFactory FACTORY = new Factory(); @@ -83,7 +83,7 @@ public RingBufferLogEvent newInstance() { private String messageFormat; private StringBuilder messageText; private Object[] parameters; - private transient Throwable thrown; + private Throwable thrown; private ThrowableProxy thrownProxy; private StringMap contextData = ContextDataFactory.createContextData(); private Marker marker; @@ -91,7 +91,7 @@ public RingBufferLogEvent newInstance() { private StackTraceElement location; private ContextStack contextStack; - private transient AsyncLogger asyncLogger; + private AsyncLogger asyncLogger; public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker, final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable, @@ -130,7 +130,8 @@ public LogEvent toImmutable() { return toMemento(); } - private void setMessage(final Message msg) { + @Override + public void setMessage(final Message msg) { if (msg instanceof ReusableMessage) { final ReusableMessage reusable = (ReusableMessage) msg; reusable.formatTo(getMessageTextForWriting()); @@ -199,16 +200,31 @@ public String getLoggerName() { return loggerName; } + @Override + public void setLoggerName(final String loggerName) { + this.loggerName = loggerName; + } + @Override public Marker getMarker() { return marker; } + @Override + public void setMarker(final Marker marker) { + this.marker = marker; + } + @Override public String getLoggerFqcn() { return fqcn; } + @Override + public void setLoggerFqcn(final String loggerFqcn) { + fqcn = loggerFqcn; + } + @Override public Level getLevel() { if (level == null) { @@ -217,6 +233,11 @@ public Level getLevel() { return level; } + @Override + public void setLevel(final Level level) { + this.level = level; + } + @Override public Message getMessage() { if (message == null) { @@ -333,6 +354,11 @@ public Throwable getThrown() { return thrown; } + @Override + public void setThrown(final Throwable thrown) { + this.thrown = thrown; + } + @Override public ThrowableProxy getThrownProxy() { // lazily instantiate the (expensive) ThrowableProxy @@ -345,11 +371,12 @@ public ThrowableProxy getThrownProxy() { } @Override - public ReadOnlyStringMap getContextData() { + public StringMap getContextData() { return contextData; } - void setContextData(final StringMap contextData) { + @Override + public void setContextData(final StringMap contextData) { this.contextData = contextData; } @@ -358,44 +385,85 @@ public ContextStack getContextStack() { return contextStack; } + @Override + public void setContextStack(final ContextStack contextStack) { + this.contextStack = contextStack; + } + @Override public long getThreadId() { return threadId; } + @Override + public void setThreadId(final long threadId) { + this.threadId = threadId; + } + @Override public String getThreadName() { return threadName; } + @Override + public void setThreadName(final String threadName) { + this.threadName = threadName; + } + @Override public int getThreadPriority() { return threadPriority; } + @Override + public void setThreadPriority(final int threadPriority) { + this.threadPriority = threadPriority; + } + @Override public StackTraceElement getSource() { return location; } + @Override + public void setSource(final StackTraceElement source) { + location = source; + } + @Override public long getTimeMillis() { return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : instant.getEpochMillisecond(); } + @Override + public void setTimeMillis(final long timeMillis) { + instant.initFromEpochMilli(timeMillis, 0); + } + @Override public Instant getInstant() { return instant; } + @Override + public void setInstant(final Instant instant) { + this.instant.initFrom(instant); + } + @Override public long getNanoTime() { return nanoTime; } + @Override + public void setNanoTime(final long nanoTime) { + this.nanoTime = nanoTime; + } + /** * Release references held by ring buffer to allow objects to be garbage-collected. */ + @Override public void clear() { this.populated = false; @@ -438,6 +506,7 @@ public void clear() { * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}. * @param builder the builder whose fields to populate */ + @Override public void initializeBuilder(final Log4jLogEvent.Builder builder) { builder.setContextData(contextData) // .setContextStack(contextStack) // diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java index aeaf73c00b7..3981551d3e5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java @@ -37,7 +37,6 @@ import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.LogEventFactory; -import org.apache.logging.log4j.core.impl.ReusableLogEventFactory; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.message.Message; @@ -253,7 +252,8 @@ protected LoggerConfig( this.includeLocation = includeLocation; this.config = config; if (properties != null && properties.length > 0) { - this.properties = List.of(properties.clone()); + // don't use List.of() here as that will create temporary iterators downstream + this.properties = Arrays.asList(properties.clone()); } else { this.properties = null; } @@ -478,7 +478,7 @@ public void log(final String loggerName, final String fqcn, final Marker marker, log(logEvent, LoggerConfigPredicate.ALL); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) - ReusableLogEventFactory.release(logEvent); + logEventFactory.recycle(logEvent); } } @@ -507,7 +507,7 @@ public void log(final String loggerName, final String fqcn, final StackTraceElem log(logEvent, LoggerConfigPredicate.ALL); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) - ReusableLogEventFactory.release(logEvent); + logEventFactory.recycle(logEvent); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java index 6e5f4f8168c..0011901fbec 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 @@ -54,6 +54,8 @@ 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.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; @@ -88,6 +90,11 @@ public DefaultBundle(final Injector injector, final PropertyEnvironment properti this.classLoader = classLoader; } + @SingletonFactory + public RecyclerFactory defaultRecyclerFactory() { + return RecyclerFactories.ofSpec(null); + } + @ConditionalOnProperty(name = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME) @SingletonFactory @Ordered(100) @@ -202,8 +209,8 @@ public LogEventFactory systemPropertyLogEventFactory() throws ClassNotFoundExcep @SingletonFactory public LogEventFactory defaultLogEventFactory( - final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) { - return isThreadLocalsEnabled() ? new ReusableLogEventFactory(injector, clock, nanoClock) : + final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock, final RecyclerFactory recyclerFactory) { + return isThreadLocalsEnabled() ? new ReusableLogEventFactory(injector, clock, nanoClock, recyclerFactory) : new DefaultLogEventFactory(injector, clock, nanoClock); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java index a0ab9354543..206a8924c59 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.async.RingBufferLogEvent; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.time.Clock; @@ -91,12 +92,8 @@ public Builder() { public Builder(final LogEvent other) { Objects.requireNonNull(other); - if (other instanceof RingBufferLogEvent) { - ((RingBufferLogEvent) other).initializeBuilder(this); - return; - } - if (other instanceof MutableLogEvent) { - ((MutableLogEvent) other).initializeBuilder(this); + if (other instanceof ReusableLogEvent) { + ((ReusableLogEvent) other).initializeBuilder(this); return; } this.loggerFqcn = other.getLoggerFqcn(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java index 476396d8414..86a29166b5c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java @@ -14,18 +14,18 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.impl; +import java.util.List; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.plugins.di.Key; -import java.util.List; - /** * */ @@ -46,4 +46,10 @@ default LogEvent createEvent( Throwable t) { return createEvent(loggerName, marker, fqcn, level, data, properties, t); } + + default void recycle(final LogEvent event) { + if (event instanceof ReusableLogEvent) { + ((ReusableLogEvent) event).clear(); + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java index 75eb5fb7f54..a009dd201ba 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java @@ -22,6 +22,7 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.async.InternalAsyncUtil; import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.Instant; @@ -34,17 +35,17 @@ import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.TimestampMessage; -import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.Strings; /** - * Mutable implementation of the {@code LogEvent} interface. + * Mutable implementation of the {@code ReusableLogEvent} interface. * @since 2.6 + * @see org.apache.logging.log4j.spi.Recycler */ -public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisitable { +public class MutableLogEvent implements ReusableLogEvent, ReusableMessage, ParameterVisitable { private static final Message EMPTY = new SimpleMessage(Strings.EMPTY); private int threadPriority; @@ -68,7 +69,6 @@ public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisi private String loggerFqcn; private StackTraceElement source; private ThreadContext.ContextStack contextStack; - transient boolean reserved = false; public MutableLogEvent() { // messageText and the parameter array are lazily initialized @@ -86,9 +86,9 @@ public LogEvent toImmutable() { } /** - * Initialize the fields of this {@code MutableLogEvent} from another event. + * Initialize the fields of this {@code ReusableLogEvent} from another event. *

- * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot. + * This method is used on async logger ringbuffer slots holding ReusableLogEvent objects in each slot. *

* * @param event the event to copy data from @@ -119,9 +119,7 @@ public void initFrom(final LogEvent event) { setMessage(event.getMessage()); } - /** - * Clears all references this event has to other objects. - */ + @Override public void clear() { loggerFqcn = null; marker = null; @@ -166,6 +164,7 @@ public String getLoggerFqcn() { return loggerFqcn; } + @Override public void setLoggerFqcn(final String loggerFqcn) { this.loggerFqcn = loggerFqcn; } @@ -175,6 +174,7 @@ public Marker getMarker() { return marker; } + @Override public void setMarker(final Marker marker) { this.marker = marker; } @@ -187,6 +187,7 @@ public Level getLevel() { return level; } + @Override public void setLevel(final Level level) { this.level = level; } @@ -196,6 +197,7 @@ public String getLoggerName() { return loggerName; } + @Override public void setLoggerName(final String loggerName) { this.loggerName = loggerName; } @@ -208,6 +210,7 @@ public Message getMessage() { return message; } + @Override public void setMessage(final Message msg) { if (msg instanceof ReusableMessage) { final ReusableMessage reusable = (ReusableMessage) msg; @@ -312,6 +315,7 @@ public Throwable getThrown() { return thrown; } + @Override public void setThrown(final Throwable thrown) { this.thrown = thrown; } @@ -330,6 +334,7 @@ public long getTimeMillis() { return instant.getEpochMillisecond(); } + @Override public void setTimeMillis(final long timeMillis) { this.instant.initFromEpochMilli(timeMillis, 0); } @@ -339,6 +344,11 @@ public Instant getInstant() { return instant; } + @Override + public void setInstant(final Instant instant) { + this.instant.initFrom(instant); + } + /** * Returns the ThrowableProxy associated with the event, or null. * @return The ThrowableProxy associated with the event. @@ -351,6 +361,7 @@ public ThrowableProxy getThrownProxy() { return thrownProxy; } + @Override public void setSource(final StackTraceElement source) { this.source = source; } @@ -373,10 +384,11 @@ public StackTraceElement getSource() { } @Override - public ReadOnlyStringMap getContextData() { + public StringMap getContextData() { return contextData; } + @Override public void setContextData(final StringMap mutableContextData) { this.contextData = mutableContextData; } @@ -386,6 +398,7 @@ public ThreadContext.ContextStack getContextStack() { return contextStack; } + @Override public void setContextStack(final ThreadContext.ContextStack contextStack) { this.contextStack = contextStack; } @@ -395,6 +408,7 @@ public long getThreadId() { return threadId; } + @Override public void setThreadId(final long threadId) { this.threadId = threadId; } @@ -404,6 +418,7 @@ public String getThreadName() { return threadName; } + @Override public void setThreadName(final String threadName) { this.threadName = threadName; } @@ -413,6 +428,7 @@ public int getThreadPriority() { return threadPriority; } + @Override public void setThreadPriority(final int threadPriority) { this.threadPriority = threadPriority; } @@ -442,14 +458,12 @@ public long getNanoTime() { return nanoTime; } + @Override public void setNanoTime(final long nanoTime) { this.nanoTime = nanoTime; } - /** - * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}. - * @param builder the builder whose fields to populate - */ + @Override public void initializeBuilder(final Log4jLogEvent.Builder builder) { builder.setContextData(contextData) // .setContextStack(contextStack) // diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java index 3020455c06d..c4990416f50 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java @@ -16,39 +16,50 @@ */ package org.apache.logging.log4j.core.impl; +import java.util.List; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.async.ThreadNameCachingStrategy; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.plugins.Inject; -import org.apache.logging.log4j.util.StringMap; - -import java.util.List; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactory; /** - * Garbage-free LogEventFactory that reuses a single mutable log event. + * Garbage-free LogEventFactory that recycles mutable LogEvent instances. * @since 2.6 + * @see Recycler */ public class ReusableLogEventFactory implements LogEventFactory { private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create(); - private static final ThreadLocal mutableLogEventThreadLocal = new ThreadLocal<>(); - private final ContextDataInjector injector; private final Clock clock; private final NanoClock nanoClock; + private final Recycler recycler; @Inject - public ReusableLogEventFactory(final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) { + public ReusableLogEventFactory(final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock, + final RecyclerFactory recyclerFactory) { this.injector = injector; this.clock = clock; this.nanoClock = nanoClock; + this.recycler = recyclerFactory.create(() -> { + final MutableLogEvent event = new MutableLogEvent(); + final Thread currentThread = Thread.currentThread(); + event.setThreadId(currentThread.getId()); + event.setThreadName(currentThread.getName()); + event.setThreadPriority(currentThread.getPriority()); + return event; + }); } /** @@ -86,8 +97,7 @@ public LogEvent createEvent(final String loggerName, final Marker marker, public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn, final StackTraceElement location, final Level level, final Message message, final List properties, final Throwable t) { - MutableLogEvent result = getOrCreateMutableLogEvent(); - result.reserved = true; + MutableLogEvent result = recycler.acquire(); // No need to clear here, values are cleared in release when reserved is set to false. // If the event was dirty we'd create a new one. @@ -99,7 +109,7 @@ public LogEvent createEvent(final String loggerName, final Marker marker, result.initTime(clock, nanoClock); result.setThrown(t); result.setSource(location); - result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData())); + result.setContextData(injector.injectContextData(properties, result.getContextData())); result.setContextStack(ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack());// mutable copy if (THREAD_NAME_CACHING_STRATEGY == ThreadNameCachingStrategy.UNCACHED) { @@ -109,22 +119,14 @@ public LogEvent createEvent(final String loggerName, final Marker marker, return result; } - private static MutableLogEvent getOrCreateMutableLogEvent() { - MutableLogEvent result = mutableLogEventThreadLocal.get(); - return result == null || result.reserved ? createInstance(result) : result; - } - - private static MutableLogEvent createInstance(MutableLogEvent existing) { - MutableLogEvent result = new MutableLogEvent(); - - // usually no need to re-initialize thread-specific fields since the event is stored in a ThreadLocal - result.setThreadId(Thread.currentThread().getId()); - result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call - result.setThreadPriority(Thread.currentThread().getPriority()); - if (existing == null) { - mutableLogEventThreadLocal.set(result); + @Override + public void recycle(final LogEvent event) { + if (event instanceof ReusableLogEvent) { + ((ReusableLogEvent) event).clear(); + if (event instanceof MutableLogEvent) { + recycler.release((MutableLogEvent) event); + } } - return result; } /** @@ -132,12 +134,12 @@ private static MutableLogEvent createInstance(MutableLogEvent existing) { * This flag is used internally to verify that a reusable log event is no longer in use and can be reused. * @param logEvent the log event to make available again * @since 2.7 + * @deprecated use {@link #recycle(LogEvent)} */ + @Deprecated(since = "3.0.0") public static void release(final LogEvent logEvent) { // LOG4J2-1583 - if (logEvent instanceof MutableLogEvent) { - final MutableLogEvent mutableLogEvent = (MutableLogEvent) logEvent; - mutableLogEvent.clear(); - mutableLogEvent.reserved = false; + if (logEvent instanceof ReusableLogEvent) { + ((ReusableLogEvent) logEvent).clear(); } } } From de66f2074d723ccbad4ad37027ee635f8e2efc09 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Mon, 16 Jan 2023 15:12:31 -0600 Subject: [PATCH 06/39] Update RecyclerFactory API and use in more scenarios - Split cleaner concerns in RecyclerFactory: eager versus lazy cleaning. This makes the Recycler API work naturally with existing StringBuilder post-usage trimming. - Uses a simpler method for checking for the presence of JCTools. Given that it's an optional (static) dependency, we can compile direct references to it in classes and see if our classes throw LinkageError-related exceptions at runtime when attempting to link to JCTools. - Rename QueueSupplier to QueueFactory - Extract a Queues util for easier access to different JCTools queue implementations when available - Update implementations of AbstractStringLayout to use Recycler API - CsvLogEventLayout - CsvParameterLayout - GelfLayout - HtmlLayout - Log4j1SyslogLayout - Log4j1XmlLayout - PatternLayout - Rfc5424Layout - SyslogLayout Signed-off-by: Matt Sicker --- .../log4j/layout/Log4j1SyslogLayout.java | 48 ++-- .../apache/log4j/layout/Log4j1XmlLayout.java | 32 ++- .../log4j/message/ReusableMessageFactory.java | 32 ++- .../message/ReusableParameterizedMessage.java | 16 +- .../log4j/spi/DummyRecyclerFactory.java | 3 +- .../log4j/spi/QueueingRecyclerFactory.java | 28 +- .../logging/log4j/spi/RecyclerFactories.java | 179 ++----------- .../logging/log4j/spi/RecyclerFactory.java | 27 +- .../log4j/spi/ThreadLocalRecyclerFactory.java | 22 +- .../QueueFactory.java} | 4 +- .../org/apache/logging/log4j/util/Queues.java | 200 ++++++++++++++ .../core/layout/AbstractStringLayoutTest.java | 19 +- .../log4j/core/async/AsyncLoggerConfig.java | 14 +- .../log4j/core/config/Configuration.java | 18 +- .../log4j/core/config/LoggerConfig.java | 4 +- .../log4j/core/impl/DefaultBundle.java | 2 +- .../log4j/core/layout/AbstractLayout.java | 19 +- .../core/layout/AbstractStringLayout.java | 66 +++-- .../logging/log4j/core/layout/GelfLayout.java | 157 ++++++----- .../logging/log4j/core/layout/HtmlLayout.java | 252 +++++++++--------- .../log4j/core/layout/PatternLayout.java | 76 +++--- .../log4j/core/layout/Rfc5424Layout.java | 76 +++--- .../log4j/core/layout/SyslogLayout.java | 42 +-- .../log4j/csv/layout/CsvLogEventLayout.java | 10 +- .../log4j/csv/layout/CsvParameterLayout.java | 10 +- ...ctStringLayoutStringEncodingBenchmark.java | 23 +- 26 files changed, 803 insertions(+), 576 deletions(-) rename log4j-api/src/main/java/org/apache/logging/log4j/{spi/QueueSupplier.java => util/QueueFactory.java} (92%) create mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java index 0378e02c880..0787dbbd455 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java @@ -166,32 +166,36 @@ public String toSerializable(final LogEvent event) { // so we generate the message first final String message = messageLayout != null ? messageLayout.toSerializable(event) : event.getMessage().getFormattedMessage(); - final StringBuilder buf = getStringBuilder(); - - buf.append('<'); - buf.append(Priority.getPriority(facility, event.getLevel())); - buf.append('>'); - - if (header) { - final int index = buf.length() + 4; - dateConverter.format(event, buf); - // RFC 3164 says leading space, not leading zero on days 1-9 - if (buf.charAt(index) == '0') { - buf.setCharAt(index, Chars.SPACE); + final StringBuilder buf = acquireStringBuilder(); + + try { + buf.append('<'); + buf.append(Priority.getPriority(facility, event.getLevel())); + buf.append('>'); + + if (header) { + final int index = buf.length() + 4; + dateConverter.format(event, buf); + // RFC 3164 says leading space, not leading zero on days 1-9 + if (buf.charAt(index) == '0') { + buf.setCharAt(index, Chars.SPACE); + } + + buf.append(Chars.SPACE); + buf.append(localHostname); + buf.append(Chars.SPACE); } - buf.append(Chars.SPACE); - buf.append(localHostname); - buf.append(Chars.SPACE); - } + if (facilityPrinting) { + buf.append(facility != null ? facility.name().toLowerCase() : "user").append(':'); + } - if (facilityPrinting) { - buf.append(facility != null ? facility.name().toLowerCase() : "user").append(':'); + buf.append(message); + // TODO: splitting message into 1024 byte chunks? + return buf.toString(); + } finally { + releaseStringBuilder(buf); } - - buf.append(message); - // TODO: splitting message into 1024 byte chunks? - return buf.toString(); } /** diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java index 4eb629c62a7..4f48fdd0912 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java @@ -16,6 +16,12 @@ */ package org.apache.log4j.layout; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.layout.AbstractStringLayout; @@ -28,12 +34,6 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.Strings; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Objects; - /** * Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations. * @@ -75,16 +75,24 @@ public boolean isProperties() { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder text = getStringBuilder(); - formatTo(event, text); - getStringBuilderEncoder().encode(text, destination); + final StringBuilder text = acquireStringBuilder(); + try { + formatTo(event, text); + getStringBuilderEncoder().encode(text, destination); + } finally { + releaseStringBuilder(text); + } } @Override public String toSerializable(final LogEvent event) { - final StringBuilder text = getStringBuilder(); - formatTo(event, text); - return text.toString(); + final StringBuilder text = acquireStringBuilder(); + try { + formatTo(event, text); + return text.toString(); + } finally { + releaseStringBuilder(text); + } } private void formatTo(final LogEvent event, final StringBuilder buf) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index e8c2794f7d4..3ad45eaf5de 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 @@ -48,14 +48,23 @@ public final class ReusableMessageFactory implements MessageFactory { * Constructs a message factory using the default {@link RecyclerFactory}. */ public ReusableMessageFactory() { - this(RecyclerFactories.ofSpec(null)); + this(RecyclerFactories.getDefault()); } public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { super(); - parameterizedMessageRecycler = recyclerFactory.create(ReusableParameterizedMessage::new); - simpleMessageRecycler = recyclerFactory.create(ReusableSimpleMessage::new); - objectMessageRecycler = recyclerFactory.create(ReusableObjectMessage::new); + parameterizedMessageRecycler = recyclerFactory.create( + ReusableParameterizedMessage::new, + RecyclerFactory.defaultCleaner(), + ReusableParameterizedMessage::clear); + simpleMessageRecycler = recyclerFactory.create( + ReusableSimpleMessage::new, + RecyclerFactory.defaultCleaner(), + ReusableSimpleMessage::clear); + objectMessageRecycler = recyclerFactory.create( + ReusableObjectMessage::new, + RecyclerFactory.defaultCleaner(), + ReusableObjectMessage::clear); } /** @@ -77,15 +86,12 @@ public static void release(final Message message) { // LOG4J2-1583 @Override public void recycle(final Message message) { // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today! - if (message instanceof ReusableMessage) { - ((ReusableMessage) message).clear(); - 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); - } + 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); } } 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 83c06a7e986..513e952e53e 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 @@ -52,7 +52,7 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV * Creates a reusable message. */ public ReusableParameterizedMessage() { - this(RecyclerFactories.ofSpec(null)); + this(RecyclerFactories.getDefault()); } public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { @@ -61,7 +61,8 @@ public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length(); return new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2)); }, - buffer -> buffer.setLength(0) + buffer -> buffer.setLength(0), + buffer -> StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE) ); } @@ -314,11 +315,12 @@ public Throwable getThrowable() { @Override public String getFormattedMessage() { final StringBuilder sb = bufferRecycler.acquire(); - formatTo(sb); - final String result = sb.toString(); - StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); - bufferRecycler.release(sb); - return result; + try { + formatTo(sb); + return sb.toString(); + } finally { + bufferRecycler.release(sb); + } } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index f86d2c67cbb..3ca81b521d7 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -37,7 +37,8 @@ public static DummyRecyclerFactory getInstance() { @Override public Recycler create( final Supplier supplier, - final Consumer cleaner) { + final Consumer lazyCleaner, + final Consumer eagerCleaner) { return new DummyRecycler<>(supplier); } 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 index f021e93376f..3387ee47803 100644 --- 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 @@ -20,20 +20,23 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import org.apache.logging.log4j.util.QueueFactory; + public class QueueingRecyclerFactory implements RecyclerFactory { - private final QueueSupplier queueSupplier; + private final QueueFactory queueFactory; - public QueueingRecyclerFactory(final QueueSupplier queueSupplier) { - this.queueSupplier = queueSupplier; + public QueueingRecyclerFactory(final QueueFactory queueFactory) { + this.queueFactory = queueFactory; } @Override public Recycler create( final Supplier supplier, - final Consumer cleaner) { - final Queue queue = queueSupplier.create(); - return new QueueingRecycler<>(supplier, cleaner, queue); + final Consumer lazyCleaner, + final Consumer eagerCleaner) { + final Queue queue = queueFactory.create(); + return new QueueingRecycler<>(supplier, lazyCleaner, eagerCleaner, queue); } // Visible for tests. @@ -41,16 +44,20 @@ static class QueueingRecycler implements Recycler { private final Supplier supplier; - private final Consumer cleaner; + private final Consumer lazyCleaner; + + private final Consumer eagerCleaner; private final Queue queue; private QueueingRecycler( final Supplier supplier, - final Consumer cleaner, + final Consumer lazyCleaner, + final Consumer eagerCleaner, final Queue queue) { this.supplier = supplier; - this.cleaner = cleaner; + this.lazyCleaner = lazyCleaner; + this.eagerCleaner = eagerCleaner; this.queue = queue; } @@ -65,13 +72,14 @@ public V acquire() { if (value == null) { return supplier.get(); } else { - cleaner.accept(value); + lazyCleaner.accept(value); return value; } } @Override public void release(final V value) { + eagerCleaner.accept(value); queue.offer(value); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java index d1e5ba73c9d..e660cc09681 100644 --- 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 @@ -16,20 +16,13 @@ */ package org.apache.logging.log4j.spi; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -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.Objects; +import java.util.Set; -import org.apache.logging.log4j.util.Cast; -import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.ReflectionUtil; +import org.apache.logging.log4j.util.QueueFactory; +import org.apache.logging.log4j.util.Queues; import org.apache.logging.log4j.util.StringParameterParser; -import org.jctools.queues.MpmcArrayQueue; import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; @@ -37,122 +30,23 @@ public final class RecyclerFactories { private RecyclerFactories() {} - private static final String JCTOOLS_FACTORY_CLASS_NAME = - RecyclerFactories.class.getName() + "$MpmcArrayQueueFactory"; - - private interface QueueFactory { - Queue create(final int capacity); - } - - private static class ArrayBlockingQueueFactory implements QueueFactory { - @Override - public Queue create(final int capacity) { - return new ArrayBlockingQueue<>(capacity); - } - } - - @SuppressWarnings("unused") // loaded via reflection to check for presence of JCTools - private static class MpmcArrayQueueFactory implements QueueFactory { - @Override - public Queue create(final int capacity) { - return new MpmcArrayQueue<>(capacity); - } - } - - private static class ConstructedQueueFactory implements QueueFactory { - private final String queueFactorySpec; - private final Constructor constructor; - - private ConstructedQueueFactory(final String queueFactorySpec, final Constructor constructor) { - this.queueFactorySpec = queueFactorySpec; - this.constructor = constructor; - } - - @Override - public Queue create(final int capacity) { - final Constructor> typedConstructor = Cast.cast(constructor); - try { - return typedConstructor.newInstance(capacity); - } catch (InvocationTargetException e) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, e.getCause()); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, e); - } - } - } - - private static class MethodProvidedQueueFactory implements QueueFactory { - private final String queueFactorySpec; - private final Method method; - - private MethodProvidedQueueFactory(final String queueFactorySpec, final Method method) { - this.queueFactorySpec = queueFactorySpec; - this.method = method; - } - - @Override - public Queue create(final int capacity) { - try { - return Cast.cast(method.invoke(null, capacity)); - } catch (InvocationTargetException e) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, e.getCause()); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "recycler queue construction failed for factory: " + - queueFactorySpec, e); - } - } - } - - private static class QueueFactorySupplier implements QueueSupplier { - private final QueueFactory queueFactory; - private final int capacity; - - private QueueFactorySupplier(final QueueFactory queueFactory, final int capacity) { - this.queueFactory = queueFactory; - this.capacity = capacity; - } - - @Override - public Queue create() { - return queueFactory.create(capacity); - } + private static int getDefaultCapacity() { + return Math.max( + 2 * Runtime.getRuntime().availableProcessors() + 1, + 8); } - private static QueueSupplier getQueueSupplier(final int capacity) { - final ClassLoader classLoader = RecyclerFactories.class.getClassLoader(); - Class factoryClass; - try { - // try to load RecyclerFactories.MpmcArrayQueueFactory; a linkage error should occur if JCTools is unavailable - factoryClass = classLoader.loadClass(JCTOOLS_FACTORY_CLASS_NAME) - .asSubclass(QueueFactory.class); - } catch (final ClassNotFoundException | LinkageError ignored) { - factoryClass = ArrayBlockingQueueFactory.class; - } - final QueueFactory queueFactory = ReflectionUtil.instantiate(factoryClass); - return new QueueFactorySupplier(queueFactory, capacity); + public static RecyclerFactory getDefault() { + return isThreadLocalsEnabled() + ? ThreadLocalRecyclerFactory.getInstance() + : new QueueingRecyclerFactory(Queues.MPMC.factory(getDefaultCapacity())); } 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 { - return new QueueingRecyclerFactory(getQueueSupplier(defaultCapacity)); - } + return getDefault(); } // Is a dummy factory requested? @@ -167,6 +61,10 @@ else if (recyclerFactorySpec.equals("threadLocal")) { // Is a queueing factory requested? else if (recyclerFactorySpec.startsWith("queue")) { + + // Determine the default capacity. + final int defaultCapacity = getDefaultCapacity(); + return readQueueingRecyclerFactory(recyclerFactorySpec, defaultCapacity); } @@ -190,8 +88,7 @@ private static RecyclerFactory readQueueingRecyclerFactory( : 0)); final Map parsedValues = StringParameterParser.parse( - queueFactorySpec, - new LinkedHashSet<>(Arrays.asList("supplier", "capacity"))); + queueFactorySpec, Set.of("supplier", "capacity")); // Read the supplier path. final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); @@ -218,43 +115,11 @@ private static RecyclerFactory readQueueingRecyclerFactory( } // Execute the read spec. - if (supplierPath == null) { - return new QueueingRecyclerFactory(getQueueSupplier(capacity)); - } - return createRecyclerFactory(queueFactorySpec, supplierPath, capacity); + final QueueFactory queueFactory = Objects.isNull(supplierPath) + ? Queues.MPMC.factory(capacity) + : Queues.createQueueFactory(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 QueueFactory queueFactory; - if ("new".equals(supplierMethodName)) { - final Constructor supplierCtor = - supplierClass.getDeclaredConstructor(int.class); - queueFactory = new ConstructedQueueFactory(queueFactorySpec, supplierCtor); - } else { - final Method supplierMethod = - supplierClass.getMethod(supplierMethodName, int.class); - queueFactory = new MethodProvidedQueueFactory(queueFactorySpec, supplierMethod); - } - return new QueueingRecyclerFactory(new QueueFactorySupplier(queueFactory, capacity)); - } catch (final Exception error) { - throw new RuntimeException( - "failed executing queueing recycler factory: " + - queueFactorySpec, error); - } + return new QueueingRecyclerFactory(queueFactory); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java index 90b81327556..2e53074b33b 100644 --- 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 @@ -39,7 +39,7 @@ public interface RecyclerFactory { * @return a new recycler for V-type instances */ default Recycler create(final Supplier supplier) { - return create(supplier, ignored -> {}); + return create(supplier, defaultCleaner()); } /** @@ -54,6 +54,29 @@ default Recycler create(final Supplier supplier) { * @param the recyclable type * @return a new recycler for V-type instances */ - Recycler create(Supplier supplier, Consumer cleaner); + default Recycler create(Supplier supplier, Consumer cleaner) { + return create(supplier, cleaner, defaultCleaner()); + } + + /** + * Creates a new recycler using the given functions for providing fresh instances and for cleaning recycled + * instances lazily or eagerly. The lazy cleaner function is invoked on recycled instances before being + * returned by {@link Recycler#acquire()}. The eager cleaner function is invoked on recycled instances + * during {@link Recycler#release(Object)}. + * + * @param supplier function to provide new instances of a recyclable object + * @param lazyCleaner function to invoke to clean a recycled object before being acquired + * @param eagerCleaner function to invoke to clean a recycled object after being released + * @param the recyclable type + * @return a new recycler for V-type instances + */ + Recycler create(Supplier supplier, Consumer lazyCleaner, Consumer eagerCleaner); + + /** + * Creates a default cleaner function that does nothing. + */ + static Consumer defaultCleaner() { + return ignored -> {}; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java index f04ba060485..95400423fa2 100644 --- 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 @@ -18,7 +18,6 @@ import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; @@ -41,8 +40,9 @@ public static ThreadLocalRecyclerFactory getInstance() { @Override public Recycler create( final Supplier supplier, - final Consumer cleaner) { - return new ThreadLocalRecycler<>(supplier, cleaner); + final Consumer lazyCleaner, + final Consumer eagerCleaner) { + return new ThreadLocalRecycler<>(supplier, lazyCleaner, eagerCleaner); } // Visible for testing @@ -50,15 +50,19 @@ static class ThreadLocalRecycler implements Recycler { private final Supplier supplier; - private final Consumer cleaner; + private final Consumer lazyCleaner; + + private final Consumer eagerCleaner; private final ThreadLocal> holder; private ThreadLocalRecycler( final Supplier supplier, - final Consumer cleaner) { + final Consumer lazyCleaner, + final Consumer eagerCleaner) { this.supplier = supplier; - this.cleaner = cleaner; + this.lazyCleaner = lazyCleaner; + this.eagerCleaner = eagerCleaner; this.holder = ThreadLocal.withInitial(ArrayDeque::new); } @@ -68,13 +72,15 @@ public V acquire() { final V value = queue.poll(); if (value == null) { return supplier.get(); + } else { + lazyCleaner.accept(value); + return value; } - cleaner.accept(value); - return value; } @Override public void release(final V value) { + eagerCleaner.accept(value); holder.get().offer(value); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java similarity index 92% rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java index 46eb0478119..53a6d6db29e 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueSupplier.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java @@ -14,10 +14,10 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.spi; +package org.apache.logging.log4j.util; import java.util.Queue; -public interface QueueSupplier { +public interface QueueFactory { Queue create(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java new file mode 100644 index 00000000000..dd80840ddab --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; + +import org.jctools.queues.MpmcArrayQueue; +import org.jctools.queues.MpscArrayQueue; +import org.jctools.queues.SpmcArrayQueue; +import org.jctools.queues.SpscArrayQueue; + +/** + * Provides {@link QueueFactory} and {@link Queue} instances for different use cases. When the + * JCTools library is included at runtime, then + * the specialized lock free or wait free queues are used from there. Otherwise, {@link ArrayBlockingQueue} + * is provided as a fallback for thread-safety. Custom implementations of {@link QueueFactory} may also be + * created via {@link #createQueueFactory(String, String, int)}. + */ +public enum Queues { + /** + * Provides a bounded queue for single-producer/single-consumer usage. + */ + SPSC(Lazy.lazy(JCToolsQueueFactory.SPSC::load)), + MPSC(Lazy.lazy(JCToolsQueueFactory.MPSC::load)), + SPMC(Lazy.lazy(JCToolsQueueFactory.SPMC::load)), + MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load)); + + private final Lazy queueFactory; + + Queues(final Lazy queueFactory) { + this.queueFactory = queueFactory; + } + + public QueueFactory factory(final int capacity) { + return new ProxyQueueFactory(queueFactory.get(), capacity); + } + + public Queue create(final int capacity) { + return queueFactory.get().create(capacity); + } + + public static QueueFactory createQueueFactory(final String queueFactorySpec, + final String supplierPath, + final int capacity) { + final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.'); + if (supplierPathSplitterIndex < 0) { + throw new IllegalArgumentException( + "invalid supplier in queue factory: " + + queueFactorySpec); + } + 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( + queueFactorySpec, supplierCtor); + } else { + final Method supplierMethod = + supplierClass.getMethod(supplierMethodName, int.class); + queueFactory = new StaticMethodProvidedQueueFactory( + queueFactorySpec, supplierMethod); + } + return new ProxyQueueFactory(queueFactory, capacity); + } catch (final ReflectiveOperationException | LinkageError | SecurityException error) { + throw new RuntimeException( + "failed executing queue factory: " + + queueFactorySpec, error); + } + } + + static class ProxyQueueFactory implements QueueFactory { + private final BoundedQueueFactory factory; + private final int capacity; + + ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) { + this.factory = factory; + this.capacity = capacity; + } + + @Override + public Queue create() { + return factory.create(capacity); + } + } + + interface BoundedQueueFactory { + Queue create(final int capacity); + } + + static class ArrayBlockingQueueFactory implements BoundedQueueFactory { + @Override + public Queue create(final int capacity) { + return new ArrayBlockingQueue<>(capacity); + } + } + + enum JCToolsQueueFactory implements BoundedQueueFactory { + SPSC { + @Override + public Queue create(final int capacity) { + return new SpscArrayQueue<>(capacity); + } + }, + MPSC { + @Override + public Queue create(final int capacity) { + return new MpscArrayQueue<>(capacity); + } + }, + SPMC { + @Override + public Queue create(final int capacity) { + return new SpmcArrayQueue<>(capacity); + } + }, + MPMC { + @Override + public Queue create(final int capacity) { + return new MpmcArrayQueue<>(capacity); + } + }; + + BoundedQueueFactory load() { + try { + // if JCTools is unavailable at runtime, then we'll only find out once we attempt to invoke + // BoundedQueueFactory::create which is the first time the ClassLoader will try to link the + // referenced JCTools class causing a NoClassDefFoundError or some other LinkageError potentially. + // also, test with a large enough capacity to avoid any IllegalArgumentExceptions from trivial queues + create(16); + return this; + } catch (final LinkageError ignored) { + return new ArrayBlockingQueueFactory(); + } + } + } + + static class ConstructorProvidedQueueFactory implements BoundedQueueFactory { + private final String queueFactorySpec; + private final Constructor constructor; + + ConstructorProvidedQueueFactory(final String queueFactorySpec, final Constructor constructor) { + this.queueFactorySpec = queueFactorySpec; + this.constructor = constructor; + } + + @Override + public Queue create(final int capacity) { + final Constructor> typedConstructor = Cast.cast(constructor); + try { + return typedConstructor.newInstance(capacity); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException( + "queue construction failed for factory: " + + queueFactorySpec, e); + } + } + } + + static class StaticMethodProvidedQueueFactory implements BoundedQueueFactory { + private final String queueFactorySpec; + private final Method method; + + StaticMethodProvidedQueueFactory(final String queueFactorySpec, final Method method) { + this.queueFactorySpec = queueFactorySpec; + this.method = method; + } + + @Override + public Queue create(final int capacity) { + try { + return Cast.cast(method.invoke(null, capacity)); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException( + "queue construction failed for factory: " + + queueFactorySpec, e); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java index 65855348f3d..257a291b167 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java @@ -1,4 +1,4 @@ -package org.apache.logging.log4j.core.layout;/* +/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -14,13 +14,15 @@ * See the license for the specific language governing permissions and * limitations under the license. */ +package org.apache.logging.log4j.core.layout; import java.nio.charset.Charset; import org.apache.logging.log4j.core.LogEvent; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests AbstractStringLayout. @@ -34,10 +36,6 @@ public ConcreteStringLayout() { super(Charset.defaultCharset()); } - public static StringBuilder getStringBuilder() { - return AbstractStringLayout.getStringBuilder(); - } - @Override public String toSerializable(final LogEvent event) { return null; @@ -46,7 +44,8 @@ public String toSerializable(final LogEvent event) { @Test public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { - final StringBuilder sb = ConcreteStringLayout.getStringBuilder(); + final ConcreteStringLayout layout = new ConcreteStringLayout(); + final StringBuilder sb = layout.acquireStringBuilder(); final int initialCapacity = sb.capacity(); assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); @@ -55,8 +54,9 @@ public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { sb.append(smallMessage); assertEquals(initialCapacity, sb.capacity(), "capacity not grown"); assertEquals(SMALL, sb.length(), "length=msg length"); + layout.releaseStringBuilder(sb); - final StringBuilder sb2 = ConcreteStringLayout.getStringBuilder(); + final StringBuilder sb2 = layout.acquireStringBuilder(); assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); assertEquals(0, sb2.length(), "empty, ready for use"); @@ -70,8 +70,9 @@ public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { sb2.setLength(0); // set 0 before next getStringBuilder() call assertEquals(0, sb2.length(), "empty, cleared"); assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, "capacity remains very large"); + layout.releaseStringBuilder(sb2); - final StringBuilder sb3 = ConcreteStringLayout.getStringBuilder(); + final StringBuilder sb3 = layout.acquireStringBuilder(); assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(), "capacity, trimmed to MAX_STRING_BUILDER_SIZE"); assertEquals(0, sb3.length(), "empty, ready for use"); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java index 7593f7fb520..15b1eea9bdb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java @@ -16,6 +16,10 @@ */ package org.apache.logging.log4j.core.async; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Filter; @@ -33,10 +37,6 @@ import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.util.Strings; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - /** * Asynchronous Logger object that is created via configuration and can be * combined with synchronous loggers. @@ -255,7 +255,7 @@ public static LoggerConfig createLogger( return new AsyncLoggerConfig(name, appenderRefs, filter, level, additive, properties, config, includeLocation(includeLocation), - config.getComponent(LogEventFactory.KEY)); + config.getLogEventFactory()); } /** @@ -278,7 +278,7 @@ public static LoggerConfig createLogger( final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) { final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; return new AsyncLoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, - includeLocation(includeLocation), config.getComponent(LogEventFactory.KEY)); + includeLocation(includeLocation), config.getLogEventFactory()); } // Note: for asynchronous loggers, includeLocation default is FALSE @@ -318,7 +318,7 @@ public static LoggerConfig createLogger(final String additivity, final Level lev final boolean additive = Booleans.parseBoolean(additivity, true); return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, properties, config, AsyncLoggerConfig.includeLocation(includeLocation), - config.getComponent(LogEventFactory.KEY)); + config.getLogEventFactory()); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java index 53fa9a4f94f..f9382bc8a28 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java @@ -16,6 +16,10 @@ */ package org.apache.logging.log4j.core.config; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; @@ -25,6 +29,7 @@ import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate; import org.apache.logging.log4j.core.async.AsyncWaitStrategyFactory; import org.apache.logging.log4j.core.filter.Filterable; +import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Advertiser; @@ -33,10 +38,7 @@ import org.apache.logging.log4j.core.util.WatchManager; import org.apache.logging.log4j.plugins.Node; import org.apache.logging.log4j.plugins.di.Key; - -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; +import org.apache.logging.log4j.spi.RecyclerFactory; /** * Interface that must be implemented to create a configuration. @@ -238,4 +240,12 @@ default T getComponent(Key key) { * @return the logger context. */ LoggerContext getLoggerContext(); + + default LogEventFactory getLogEventFactory() { + return getComponent(LogEventFactory.KEY); + } + + default RecyclerFactory getRecyclerFactory() { + return getComponent(Key.forClass(RecyclerFactory.class)); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java index 3981551d3e5..5cf962ba26c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java @@ -656,7 +656,7 @@ public static LoggerConfig createLogger( final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) { final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, - includeLocation(includeLocation, config), config.getComponent(LogEventFactory.KEY)); + includeLocation(includeLocation, config), config.getLogEventFactory()); } // Note: for asynchronous loggers, includeLocation default is FALSE, @@ -817,7 +817,7 @@ public static LoggerConfig createLogger( return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, properties, config, includeLocation(includeLocation, config), - config.getComponent(LogEventFactory.KEY)); + config.getLogEventFactory()); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java index 0011901fbec..1644be057d8 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 @@ -92,7 +92,7 @@ public DefaultBundle(final Injector injector, final PropertyEnvironment properti @SingletonFactory public RecyclerFactory defaultRecyclerFactory() { - return RecyclerFactories.ofSpec(null); + return RecyclerFactories.getDefault(); } @ConditionalOnProperty(name = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java index cc221a47bd1..a0fdc153a16 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java @@ -167,25 +167,30 @@ protected void markEvent() { * Subclasses can override this method to provide a garbage-free implementation. For text-based layouts, * {@code AbstractStringLayout} provides various convenience methods to help with this: *

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

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

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


"); - appendLs(sbuf, "Log session start time " + new Date() + "
"); - appendLs(sbuf, "
"); - appendLs(sbuf, - ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - appendLs(sbuf, ""); - if (locationInfo) { - appendLs(sbuf, ""); + final StringBuilder sbuf = acquireStringBuilder(); + try { + append(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + append(sbuf, ""); + append(sbuf, "").append(title); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, "
"); + appendLs(sbuf, "Log session start time " + new Date() + "
"); + appendLs(sbuf, "
"); + appendLs(sbuf, + "
TimeThreadLevelLoggerFile:Line
"); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + appendLs(sbuf, ""); + if (locationInfo) { + appendLs(sbuf, ""); + } + appendLs(sbuf, ""); + appendLs(sbuf, ""); + return sbuf.toString().getBytes(getCharset()); + } finally { + releaseStringBuilder(sbuf); } - appendLs(sbuf, ""); - appendLs(sbuf, ""); - return sbuf.toString().getBytes(getCharset()); } /** @@ -332,11 +340,15 @@ public byte[] getHeader() { */ @Override public byte[] getFooter() { - final StringBuilder sbuf = new StringBuilder(); - appendLs(sbuf, "
TimeThreadLevelLoggerFile:LineMessage
Message
"); - appendLs(sbuf, "
"); - appendLs(sbuf, ""); - return getBytes(sbuf.toString()); + final StringBuilder sbuf = acquireStringBuilder(); + try { + appendLs(sbuf, ""); + appendLs(sbuf, "
"); + appendLs(sbuf, ""); + return getBytes(sbuf.toString()); + } finally { + releaseStringBuilder(sbuf); + } } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java index 8af7ec90379..8dd96b506b0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java @@ -38,6 +38,9 @@ import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.spi.Recycler; +import org.apache.logging.log4j.spi.RecyclerFactories; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; import org.apache.logging.log4j.util.Strings; @@ -194,22 +197,15 @@ public void serialize(final LogEvent event, final StringBuilder stringBuilder) { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder text = toText(eventSerializer, event, getStringBuilder()); - final Encoder encoder = getStringBuilderEncoder(); - encoder.encode(text, destination); - trimToMaxSize(text); - } - - /** - * Creates a text representation of the specified log event - * and writes it into the specified StringBuilder. - *

- * Implementations are free to return a new StringBuilder if they can - * detect in advance that the specified StringBuilder is too small. - */ - private StringBuilder toText(final Serializer2 serializer, final LogEvent event, - final StringBuilder destination) { - return serializer.toSerializable(event, destination); + final StringBuilder builder = acquireStringBuilder(); + StringBuilder text = builder; + try { + text = eventSerializer.toSerializable(event, builder); + final Encoder encoder = getStringBuilderEncoder(); + encoder.encode(text, destination); + } finally { + releaseStringBuilder(text); + } } /** @@ -240,21 +236,23 @@ private interface PatternSerializer extends Serializer, Serializer2 {} private static final class NoFormatPatternSerializer implements PatternSerializer { private final LogEventPatternConverter[] converters; + private final Recycler recycler; - private NoFormatPatternSerializer(final PatternFormatter[] formatters) { + private NoFormatPatternSerializer(final PatternFormatter[] formatters, final Recycler recycler) { this.converters = new LogEventPatternConverter[formatters.length]; for (int i = 0; i < formatters.length; i++) { converters[i] = formatters[i].getConverter(); } + this.recycler = recycler; } @Override public String toSerializable(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); + final StringBuilder sb = recycler.acquire(); try { return toSerializable(event, sb).toString(); } finally { - trimToMaxSize(sb); + recycler.release(sb); } } @@ -285,18 +283,20 @@ public String toString() { private static final class PatternFormatterPatternSerializer implements PatternSerializer { private final PatternFormatter[] formatters; + private final Recycler recycler; - private PatternFormatterPatternSerializer(final PatternFormatter[] formatters) { + private PatternFormatterPatternSerializer(final PatternFormatter[] formatters, final Recycler recycler) { this.formatters = formatters; + this.recycler = recycler; } @Override public String toSerializable(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); + final StringBuilder sb = recycler.acquire(); try { return toSerializable(event, sb).toString(); } finally { - trimToMaxSize(sb); + recycler.release(sb); } } @@ -321,19 +321,22 @@ private static final class PatternSerializerWithReplacement implements Serialize private final PatternSerializer delegate; private final RegexReplacement replace; + private final Recycler recycler; - private PatternSerializerWithReplacement(final PatternSerializer delegate, final RegexReplacement replace) { + private PatternSerializerWithReplacement(final PatternSerializer delegate, final RegexReplacement replace, + final Recycler recycler) { this.delegate = delegate; this.replace = replace; + this.recycler = recycler; } @Override public String toSerializable(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); + final StringBuilder sb = recycler.acquire(); try { return toSerializable(event, sb).toString(); } finally { - trimToMaxSize(sb); + recycler.release(sb); } } @@ -381,6 +384,10 @@ public Serializer build() { if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) { return null; } + final RecyclerFactory recyclerFactory = configuration != null + ? configuration.getRecyclerFactory() + : RecyclerFactories.getDefault(); + final Recycler recycler = createRecycler(recyclerFactory); if (patternSelector == null) { try { final PatternParser parser = createPatternParser(configuration); @@ -396,14 +403,16 @@ public Serializer build() { } } PatternSerializer serializer = hasFormattingInfo - ? new PatternFormatterPatternSerializer(formatters) - : new NoFormatPatternSerializer(formatters); - return replace == null ? serializer : new PatternSerializerWithReplacement(serializer, replace); + ? new PatternFormatterPatternSerializer(formatters, recycler) + : new NoFormatPatternSerializer(formatters, recycler); + return replace == null + ? serializer + : new PatternSerializerWithReplacement(serializer, replace, recycler); } catch (final RuntimeException ex) { throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex); } } - return new PatternSelectorSerializer(patternSelector, replace); + return new PatternSelectorSerializer(patternSelector, replace, recycler); } public SerializerBuilder setConfiguration(final Configuration configuration) { @@ -452,20 +461,23 @@ private static final class PatternSelectorSerializer implements Serializer, Seri private final PatternSelector patternSelector; private final RegexReplacement replace; + private final Recycler recycler; - private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace) { + private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace, + final Recycler recycler) { super(); this.patternSelector = patternSelector; this.replace = replace; + this.recycler = recycler; } @Override public String toSerializable(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); + final StringBuilder sb = recycler.acquire(); try { return toSerializable(event, sb).toString(); } finally { - trimToMaxSize(sb); + recycler.release(sb); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java index f18b58025cf..23bfacf544b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java @@ -274,24 +274,28 @@ public Map getContentFormat() { */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder buf = getStringBuilder(); - appendPriority(buf, event.getLevel()); - appendTimestamp(buf, event.getTimeMillis()); - appendSpace(buf); - appendHostName(buf); - appendSpace(buf); - appendAppName(buf); - appendSpace(buf); - appendProcessId(buf); - appendSpace(buf); - appendMessageId(buf, event.getMessage()); - appendSpace(buf); - appendStructuredElements(buf, event); - appendMessage(buf, event); - if (useTlsMessageFormat) { - return new TlsSyslogFrame(buf.toString()).toString(); - } - return buf.toString(); + final StringBuilder buf = acquireStringBuilder(); + try { + appendPriority(buf, event.getLevel()); + appendTimestamp(buf, event.getTimeMillis()); + appendSpace(buf); + appendHostName(buf); + appendSpace(buf); + appendAppName(buf); + appendSpace(buf); + appendProcessId(buf); + appendSpace(buf); + appendMessageId(buf, event.getMessage()); + appendSpace(buf); + appendStructuredElements(buf, event); + appendMessage(buf, event); + if (useTlsMessageFormat) { + return new TlsSyslogFrame(buf.toString()).toString(); + } + return buf.toString(); + } finally { + releaseStringBuilder(buf); + } } private void appendPriority(final StringBuilder buffer, final Level logLevel) { @@ -590,25 +594,25 @@ public String toString() { /** * Create the RFC 5424 Layout. * - * @param facility The Facility is used to try to classify the message. - * @param id The default structured data id to use when formatting according to RFC 5424. - * @param enterpriseNumber The IANA enterprise number. - * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog - * record. Defaults to "true:. - * @param mdcId The id to use for the MDC Structured Data Element. - * @param mdcPrefix The prefix to add to MDC key names. - * @param eventPrefix The prefix to add to event key names. - * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. - * @param escapeNL String that should be used to replace newlines within the message text. - * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. - * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. - * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. - * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. - * @param required A comma separated list of MDC keys that must be present in the MDC. - * @param exceptionPattern The pattern for formatting exceptions. + * @param facility The Facility is used to try to classify the message. + * @param id The default structured data id to use when formatting according to RFC 5424. + * @param enterpriseNumber The IANA enterprise number. + * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog + * record. Defaults to "true:. + * @param mdcId The id to use for the MDC Structured Data Element. + * @param mdcPrefix The prefix to add to MDC key names. + * @param eventPrefix The prefix to add to event key names. + * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. + * @param escapeNL String that should be used to replace newlines within the message text. + * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. + * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. + * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. + * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. + * @param required A comma separated list of MDC keys that must be present in the MDC. + * @param exceptionPattern The pattern for formatting exceptions. * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425. - * @param loggerFields Container for the KeyValuePairs containing the patterns - * @param config The Configuration. Some Converters require access to the Interpolator. + * @param loggerFields Container for the KeyValuePairs containing the patterns + * @param config The Configuration. Some Converters require access to the Interpolator. * @return An Rfc5424Layout. * @deprecated Use {@link Rfc5424LayoutBuilder instead} */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java index fb481233b62..be88b18009d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java @@ -145,26 +145,30 @@ protected SyslogLayout(final Facility facility, final boolean includeNL, final S */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder buf = getStringBuilder(); - - buf.append('<'); - buf.append(Priority.getPriority(facility, event.getLevel())); - buf.append('>'); - addDate(event.getTimeMillis(), buf); - buf.append(Chars.SPACE); - buf.append(localHostname); - buf.append(Chars.SPACE); - - String message = event.getMessage().getFormattedMessage(); - if (null != escapeNewLine) { - message = NEWLINE_PATTERN.matcher(message).replaceAll(escapeNewLine); + final StringBuilder buf = acquireStringBuilder(); + + try { + buf.append('<'); + buf.append(Priority.getPriority(facility, event.getLevel())); + buf.append('>'); + addDate(event.getTimeMillis(), buf); + buf.append(Chars.SPACE); + buf.append(localHostname); + buf.append(Chars.SPACE); + + String message = event.getMessage().getFormattedMessage(); + if (null != escapeNewLine) { + message = NEWLINE_PATTERN.matcher(message).replaceAll(escapeNewLine); + } + buf.append(message); + + if (includeNewLine) { + buf.append('\n'); + } + return buf.toString(); + } finally { + releaseStringBuilder(buf); } - buf.append(message); - - if (includeNewLine) { - buf.append('\n'); - } - return buf.toString(); } private synchronized void addDate(final long timestamp, final StringBuilder buf) { diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java index fa38c66d1df..216af9a7f13 100644 --- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java +++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.csv.layout; +import java.io.IOException; +import java.nio.charset.Charset; + import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.QuoteMode; import org.apache.logging.log4j.core.Layout; @@ -28,9 +31,6 @@ import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; -import java.io.IOException; -import java.nio.charset.Charset; - /** * A Comma-Separated Value (CSV) layout to log events. * @@ -77,7 +77,7 @@ protected CsvLogEventLayout(final Configuration config, final Charset charset, f @Override public String toSerializable(final LogEvent event) { - final StringBuilder buffer = getStringBuilder(); + final StringBuilder buffer = acquireStringBuilder(); final CSVFormat format = getFormat(); try { format.print(event.getNanoTime(), buffer, true); @@ -99,6 +99,8 @@ public String toSerializable(final LogEvent event) { } catch (final IOException e) { StatusLogger.getLogger().error(event.toString(), e); return format.getCommentMarker() + " " + e; + } finally { + releaseStringBuilder(buffer); } } diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java index 3a0eac0b817..cd83093fa0f 100644 --- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java +++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.csv.layout; +import java.io.IOException; +import java.nio.charset.Charset; + import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.QuoteMode; import org.apache.logging.log4j.core.Layout; @@ -29,9 +32,6 @@ import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; -import java.io.IOException; -import java.nio.charset.Charset; - /** * A Comma-Separated Value (CSV) layout to log event parameters. * The event message is currently ignored. @@ -88,13 +88,15 @@ public CsvParameterLayout(final Configuration config, final Charset charset, fin public String toSerializable(final LogEvent event) { final Message message = event.getMessage(); final Object[] parameters = message.getParameters(); - final StringBuilder buffer = getStringBuilder(); + final StringBuilder buffer = acquireStringBuilder(); try { getFormat().printRecord(buffer, parameters); return buffer.toString(); } catch (final IOException e) { StatusLogger.getLogger().error(message, e); return getFormat().getCommentMarker() + " " + e; + } finally { + releaseStringBuilder(buffer); } } diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java index 959f6b2bb69..1020250bcd9 100644 --- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java +++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.perf.jmh; import java.nio.ByteBuffer; @@ -215,9 +214,13 @@ public String toSerializable(final LogEvent event) { @Override public byte[] toByteArray(final LogEvent event) { - final StringBuilder sb = getStringBuilder(); - ((StringBuilderFormattable) event.getMessage()).formatTo(sb); - return getBytes(sb.toString()); + final StringBuilder sb = acquireStringBuilder(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + return getBytes(sb.toString()); + } finally { + releaseStringBuilder(sb); + } } } @@ -238,10 +241,14 @@ public byte[] toByteArray(final LogEvent event) { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder sb = getStringBuilder(); - ((StringBuilderFormattable) event.getMessage()).formatTo(sb); - final Encoder helper = getStringBuilderEncoder(); - helper.encode(sb, destination); + final StringBuilder sb = acquireStringBuilder(); + try { + ((StringBuilderFormattable) event.getMessage()).formatTo(sb); + final Encoder helper = getStringBuilderEncoder(); + helper.encode(sb, destination); + } finally { + releaseStringBuilder(sb); + } } } From f2f43d7a9d3f8fb11c099a0e92650dfcdc77b097 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Mon, 16 Jan 2023 15:21:18 -0600 Subject: [PATCH 07/39] Use a bounded queue for recursion --- .../logging/log4j/spi/ThreadLocalRecyclerFactory.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index 95400423fa2..c2441cefc2c 100644 --- 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 @@ -16,11 +16,12 @@ */ package org.apache.logging.log4j.spi; -import java.util.ArrayDeque; import java.util.Queue; import java.util.function.Consumer; import java.util.function.Supplier; +import org.apache.logging.log4j.util.Queues; + /** * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy * may not be appropriate in workloads where units of work are independent of operating system threads such as @@ -63,7 +64,8 @@ private ThreadLocalRecycler( this.supplier = supplier; this.lazyCleaner = lazyCleaner; this.eagerCleaner = eagerCleaner; - this.holder = ThreadLocal.withInitial(ArrayDeque::new); + // allow for some reasonable level of recursive calls before we stop caring to reuse things + this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(8)); } @Override From 633938ff383f3d07080d6b2f8e74354e575835fe Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Mon, 16 Jan 2023 19:48:01 -0600 Subject: [PATCH 08/39] Update StatusLogger to use MPMC queue Signed-off-by: Matt Sicker --- .../logging/log4j/status/StatusLogger.java | 58 +++---------------- .../org/apache/logging/log4j/util/Queues.java | 16 ++++- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java index 018d4a35a81..d25e3aedf62 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java @@ -18,15 +18,11 @@ import java.io.Closeable; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.logging.log4j.Level; @@ -38,6 +34,7 @@ import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.spi.LoggingSystemProperties; import org.apache.logging.log4j.util.LowLevelLogUtil; +import org.apache.logging.log4j.util.Queues; /** * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}. @@ -68,8 +65,6 @@ public final class StatusLogger extends AbstractLogger { private final Queue messages; - private final Lock msgLock = new ReentrantLock(); - private int listenersLevel; /** @@ -102,7 +97,7 @@ public final class StatusLogger extends AbstractLogger { this.logger = logger; this.configuration = configuration; this.listenersLevel = configuration.getDefaultLevel().intLevel(); - messages = new BoundedQueue<>(configuration.getMaxEntries()); + messages = Queues.MPMC.create(configuration.getMaxEntries()); } /** @@ -178,6 +173,7 @@ public Iterable getListeners() { * Clears the list of status events and listeners. */ public void reset() { + messages.clear(); listenersLock.writeLock().lock(); try { for (final StatusListener listener : listeners) { @@ -186,8 +182,6 @@ public void reset() { } finally { listeners.clear(); listenersLock.writeLock().unlock(); - // note this should certainly come after the unlock to avoid unnecessary nested locking - clear(); } } @@ -205,24 +199,14 @@ private static void closeSilently(final Closeable resource) { * @return The list of StatusData objects. */ public List getStatusData() { - msgLock.lock(); - try { - return new ArrayList<>(messages); - } finally { - msgLock.unlock(); - } + return List.copyOf(messages); } /** * Clears the list of status events. */ public void clear() { - msgLock.lock(); - try { - messages.clear(); - } finally { - msgLock.unlock(); - } + messages.clear(); } @Override @@ -247,11 +231,9 @@ public void logMessage(final String fqcn, final Level level, final Marker marker element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); } final StatusData data = new StatusData(element, level, msg, t, null); - msgLock.lock(); - try { - messages.add(data); - } finally { - msgLock.unlock(); + while (!messages.offer(data)) { + // discard an old message and retry + messages.poll(); } // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled if (configuration.isDebugEnabled() || listeners.isEmpty()) { @@ -393,28 +375,4 @@ public boolean isEnabled(final Level level, final Marker marker) { return logger.isEnabled(level, marker); } - /** - * Queues for status events. - * - * @param Object type to be stored in the queue. - */ - private class BoundedQueue extends ConcurrentLinkedQueue { - - private static final long serialVersionUID = -3945953719763255337L; - - private final int size; - - BoundedQueue(final int size) { - this.size = size; - } - - @Override - public boolean add(final E object) { - super.add(object); - while (messages.size() > size) { - messages.poll(); - } - return size > 0; - } - } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java index dd80840ddab..d1f8622f1f3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java @@ -33,13 +33,27 @@ * is provided as a fallback for thread-safety. Custom implementations of {@link QueueFactory} may also be * created via {@link #createQueueFactory(String, String, int)}. */ +@InternalApi public enum Queues { /** - * Provides a bounded queue for single-producer/single-consumer usage. + * Provides a bounded queue for single-producer/single-consumer usage. Only one thread may offer objects + * while only one thread may poll for them. */ SPSC(Lazy.lazy(JCToolsQueueFactory.SPSC::load)), + /** + * Provides a bounded queue for multi-producer/single-consumer usage. Any thread may offer objects while only + * one thread may poll for them. + */ MPSC(Lazy.lazy(JCToolsQueueFactory.MPSC::load)), + /** + * Provides a bounded queue for single-producer/multi-consumer usage. Only one thread may offer objects but + * any thread may poll for them. + */ SPMC(Lazy.lazy(JCToolsQueueFactory.SPMC::load)), + /** + * Provides a bounded queue for multi-producer/multi-consumer usage. Any thread may offer objects and any thread + * may poll for them. + */ MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load)); private final Lazy queueFactory; From ab7b640626955742766f05a3817486fb522ec32f Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 14:53:00 -0600 Subject: [PATCH 09/39] Update log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Volkan Yazıcı --- .../logging/log4j/spi/RecyclerFactories.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) 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 index e660cc09681..f250af8540e 100644 --- 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 @@ -90,15 +90,6 @@ private static RecyclerFactory readQueueingRecyclerFactory( StringParameterParser.parse( queueFactorySpec, Set.of("supplier", "capacity")); - // Read the supplier path. - final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); - final String supplierPath; - if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) { - supplierPath = null; - } else { - supplierPath = supplierValue.toString(); - } - // Read the capacity. final StringParameterParser.Value capacityValue = parsedValues.get("capacity"); final int capacity; @@ -114,10 +105,17 @@ private static RecyclerFactory readQueueingRecyclerFactory( } } + // Read the supplier path. + final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); + final String supplierPath; + if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) { + supplierPath = Queues.MPMC.factory(capacity); + } else { + supplierPath = supplierValue.toString(); + } + // Execute the read spec. - final QueueFactory queueFactory = Objects.isNull(supplierPath) - ? Queues.MPMC.factory(capacity) - : Queues.createQueueFactory(queueFactorySpec, supplierPath, capacity); + final QueueFactory queueFactory = Queues.createQueueFactory(queueFactorySpec, supplierPath, capacity); return new QueueingRecyclerFactory(queueFactory); } From 0b9614627ea12c5e05132d8c7677ae2d0f08251a Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:35:39 -0600 Subject: [PATCH 10/39] Minimize visibility of inner classes Signed-off-by: Matt Sicker --- .../org/apache/logging/log4j/util/Queues.java | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java index d1f8622f1f3..2f0167f2ea9 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java @@ -31,7 +31,7 @@ * JCTools library is included at runtime, then * the specialized lock free or wait free queues are used from there. Otherwise, {@link ArrayBlockingQueue} * is provided as a fallback for thread-safety. Custom implementations of {@link QueueFactory} may also be - * created via {@link #createQueueFactory(String, String, int)}. + * created via {@link #createQueueFactory(String, int)}. */ @InternalApi public enum Queues { @@ -70,14 +70,10 @@ public Queue create(final int capacity) { return queueFactory.get().create(capacity); } - public static QueueFactory createQueueFactory(final String queueFactorySpec, - final String supplierPath, - final int capacity) { + public static QueueFactory createQueueFactory(final String supplierPath, final int capacity) { final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.'); if (supplierPathSplitterIndex < 0) { - throw new IllegalArgumentException( - "invalid supplier in queue factory: " + - queueFactorySpec); + throw new IllegalArgumentException("invalid supplier in queue factory: " + supplierPath); } final String supplierClassName = supplierPath.substring(0, supplierPathSplitterIndex); final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1); @@ -87,27 +83,23 @@ public static QueueFactory createQueueFactory(final String queueFactorySpec, if ("new".equals(supplierMethodName)) { final Constructor supplierCtor = supplierClass.getDeclaredConstructor(int.class); - queueFactory = new ConstructorProvidedQueueFactory( - queueFactorySpec, supplierCtor); + queueFactory = new ConstructorProvidedQueueFactory(supplierCtor); } else { final Method supplierMethod = supplierClass.getMethod(supplierMethodName, int.class); - queueFactory = new StaticMethodProvidedQueueFactory( - queueFactorySpec, supplierMethod); + queueFactory = new StaticMethodProvidedQueueFactory(supplierMethod); } return new ProxyQueueFactory(queueFactory, capacity); } catch (final ReflectiveOperationException | LinkageError | SecurityException error) { - throw new RuntimeException( - "failed executing queue factory: " + - queueFactorySpec, error); + throw new RuntimeException("failed executing queue factory", error); } } - static class ProxyQueueFactory implements QueueFactory { + private static class ProxyQueueFactory implements QueueFactory { private final BoundedQueueFactory factory; private final int capacity; - ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) { + private ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) { this.factory = factory; this.capacity = capacity; } @@ -118,18 +110,18 @@ public Queue create() { } } - interface BoundedQueueFactory { + private interface BoundedQueueFactory { Queue create(final int capacity); } - static class ArrayBlockingQueueFactory implements BoundedQueueFactory { + private static class ArrayBlockingQueueFactory implements BoundedQueueFactory { @Override public Queue create(final int capacity) { return new ArrayBlockingQueue<>(capacity); } } - enum JCToolsQueueFactory implements BoundedQueueFactory { + private enum JCToolsQueueFactory implements BoundedQueueFactory { SPSC { @Override public Queue create(final int capacity) { @@ -169,12 +161,10 @@ BoundedQueueFactory load() { } } - static class ConstructorProvidedQueueFactory implements BoundedQueueFactory { - private final String queueFactorySpec; + private static class ConstructorProvidedQueueFactory implements BoundedQueueFactory { private final Constructor constructor; - ConstructorProvidedQueueFactory(final String queueFactorySpec, final Constructor constructor) { - this.queueFactorySpec = queueFactorySpec; + private ConstructorProvidedQueueFactory(final Constructor constructor) { this.constructor = constructor; } @@ -184,19 +174,15 @@ public Queue create(final int capacity) { try { return typedConstructor.newInstance(capacity); } catch (final ReflectiveOperationException e) { - throw new RuntimeException( - "queue construction failed for factory: " + - queueFactorySpec, e); + throw new RuntimeException("queue construction failed for factory", e); } } } - static class StaticMethodProvidedQueueFactory implements BoundedQueueFactory { - private final String queueFactorySpec; + private static class StaticMethodProvidedQueueFactory implements BoundedQueueFactory { private final Method method; - StaticMethodProvidedQueueFactory(final String queueFactorySpec, final Method method) { - this.queueFactorySpec = queueFactorySpec; + private StaticMethodProvidedQueueFactory(final Method method) { this.method = method; } @@ -205,9 +191,7 @@ public Queue create(final int capacity) { try { return Cast.cast(method.invoke(null, capacity)); } catch (final ReflectiveOperationException e) { - throw new RuntimeException( - "queue construction failed for factory: " + - queueFactorySpec, e); + throw new RuntimeException("queue construction failed for factory", e); } } } From 160670f7372166af7c7d71feb963197458cd54b6 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:35:57 -0600 Subject: [PATCH 11/39] Fix RecyclerFactories Signed-off-by: Matt Sicker --- .../apache/logging/log4j/spi/RecyclerFactories.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 index f250af8540e..665bd2b215a 100644 --- 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 @@ -17,7 +17,6 @@ package org.apache.logging.log4j.spi; import java.util.Map; -import java.util.Objects; import java.util.Set; import org.apache.logging.log4j.util.QueueFactory; @@ -109,13 +108,18 @@ private static RecyclerFactory readQueueingRecyclerFactory( final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); final String supplierPath; if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) { - supplierPath = Queues.MPMC.factory(capacity); + supplierPath = null; } else { supplierPath = supplierValue.toString(); } // Execute the read spec. - final QueueFactory queueFactory = Queues.createQueueFactory(queueFactorySpec, supplierPath, capacity); + final QueueFactory queueFactory; + if (supplierPath != null) { + queueFactory = Queues.createQueueFactory(supplierPath, capacity); + } else { + queueFactory = Queues.MPMC.factory(capacity); + } return new QueueingRecyclerFactory(queueFactory); } From 13d498fd126fe6bbf4f95e326126447165bdcb9f Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:36:14 -0600 Subject: [PATCH 12/39] Rename defaultCleaner() to getDefaultCleaner() Signed-off-by: Matt Sicker --- .../logging/log4j/message/ReusableMessageFactory.java | 6 +++--- .../java/org/apache/logging/log4j/spi/RecyclerFactory.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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 3ad45eaf5de..7177712a371 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 @@ -55,15 +55,15 @@ public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { super(); parameterizedMessageRecycler = recyclerFactory.create( ReusableParameterizedMessage::new, - RecyclerFactory.defaultCleaner(), + RecyclerFactory.getDefaultCleaner(), ReusableParameterizedMessage::clear); simpleMessageRecycler = recyclerFactory.create( ReusableSimpleMessage::new, - RecyclerFactory.defaultCleaner(), + RecyclerFactory.getDefaultCleaner(), ReusableSimpleMessage::clear); objectMessageRecycler = recyclerFactory.create( ReusableObjectMessage::new, - RecyclerFactory.defaultCleaner(), + RecyclerFactory.getDefaultCleaner(), ReusableObjectMessage::clear); } 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 index 2e53074b33b..5bed3016911 100644 --- 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 @@ -39,7 +39,7 @@ public interface RecyclerFactory { * @return a new recycler for V-type instances */ default Recycler create(final Supplier supplier) { - return create(supplier, defaultCleaner()); + return create(supplier, getDefaultCleaner()); } /** @@ -55,7 +55,7 @@ default Recycler create(final Supplier supplier) { * @return a new recycler for V-type instances */ default Recycler create(Supplier supplier, Consumer cleaner) { - return create(supplier, cleaner, defaultCleaner()); + return create(supplier, cleaner, getDefaultCleaner()); } /** @@ -75,7 +75,7 @@ default Recycler create(Supplier supplier, Consumer cleaner) { /** * Creates a default cleaner function that does nothing. */ - static Consumer defaultCleaner() { + static Consumer getDefaultCleaner() { return ignored -> {}; } From 44744c5c0b255627512cb9c529d390f78b0ed23b Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:37:23 -0600 Subject: [PATCH 13/39] Add more tests for ThreadLocalRecyclerFactory Signed-off-by: Matt Sicker --- .../spi/ThreadLocalRecyclerFactoryTest.java | 101 +++++++++++++----- .../log4j/spi/ThreadLocalRecyclerFactory.java | 15 ++- 2 files changed, 89 insertions(+), 27 deletions(-) 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 index 2f159d17682..14428d20c97 100644 --- 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 @@ -16,36 +16,89 @@ */ package org.apache.logging.log4j.spi; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.util.Queue; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junitpioneer.jupiter.params.IntRangeSource; import static org.assertj.core.api.Assertions.assertThat; class ThreadLocalRecyclerFactoryTest { + + static class RecyclableObject { + boolean using; + boolean returned; + } + + private Recycler recycler; + + private Queue getRecyclerQueue() { + return ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue(); + } + + @BeforeEach + void setUp() { + recycler = ThreadLocalRecyclerFactory.getInstance() + .create(RecyclableObject::new, o -> { + o.using = true; + o.returned = false; + }, o -> { + o.returned = true; + o.using = false; + }); + } + + @ParameterizedTest + @IntRangeSource(from = 1, to = ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE, closed = true) + void nestedAcquiresDoNotInterfere(int acquisitionCount) { + // pool should start empty + assertThat(getRecyclerQueue()).isEmpty(); + + final List acquiredObjects = IntStream.range(0, acquisitionCount) + .mapToObj(i -> recycler.acquire()) + .collect(Collectors.toList()); + + // still nothing returned to pool + assertThat(getRecyclerQueue()).isEmpty(); + + // don't want any duplicate instances + assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects); + acquiredObjects.forEach(recycler::release); + + // and now they should be back in the pool + assertThat(getRecyclerQueue()).hasSize(acquisitionCount); + + // then reacquire them to see that they're still the same object as we've filled in + // the thread-local queue with returned objects + final List reacquiredObjects = IntStream.range(0, acquisitionCount) + .mapToObj(i -> recycler.acquire()) + .collect(Collectors.toList()); + + assertThat(reacquiredObjects).containsExactlyElementsOf(acquiredObjects); + } + @Test - void nestedAcquiresDoNotInterfere() { - final Recycler r = ThreadLocalRecyclerFactory.getInstance() - .create(AtomicInteger::new, i -> i.set(0)); - final var recycler = (ThreadLocalRecyclerFactory.ThreadLocalRecycler) r; - - assertThat(recycler.getQueue()).isEmpty(); - final AtomicInteger first = recycler.acquire(); - assertThat(recycler.getQueue()).isEmpty(); - final AtomicInteger second = recycler.acquire(); - assertThat(recycler.getQueue()).isEmpty(); - first.set(1); - second.set(2); - final AtomicInteger third = recycler.acquire(); - assertThat(recycler.getQueue()).isEmpty(); - assertThat(third.get()).isEqualTo(0); - assertThat(first.get()).isEqualTo(1); - assertThat(second.get()).isEqualTo(2); - recycler.release(first); - assertThat(recycler.getQueue()).hasSize(1); - recycler.release(second); - assertThat(recycler.getQueue()).hasSize(2); - recycler.release(third); - assertThat(recycler.getQueue()).hasSize(3); + void nestedAcquiresPastMaximumQueueSizeShouldDiscardExtraReleases() { + assertThat(getRecyclerQueue()).isEmpty(); + + // simulate a massively callstack with tons of logging + final List acquiredObjects = IntStream.range(0, 1024) + .mapToObj(i -> recycler.acquire()) + .collect(Collectors.toList()); + + // still nothing returned to pool + assertThat(getRecyclerQueue()).isEmpty(); + + // don't want any duplicate instances + assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects); + acquiredObjects.forEach(recycler::release); + + // upon return, we should only have ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE retained for future use + assertThat(getRecyclerQueue()).hasSize(ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java index c2441cefc2c..8a031c1ed98 100644 --- 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 @@ -25,10 +25,20 @@ /** * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy * may not be appropriate in workloads where units of work are independent of operating system threads such as - * reactive streams, coroutines, or virtual threads. + * reactive streams, coroutines, or virtual threads; a {@linkplain QueueingRecyclerFactory queue-based approach} + * is more flexible. + * + * @since 3.0.0 */ public class ThreadLocalRecyclerFactory implements RecyclerFactory { + // This determines the maximum number of recyclable objects we may retain per thread. + // This allows us to acquire recyclable objects in recursive method calls and maintain + // minimal overhead in the scenarios where the active instance count goes far beyond this + // for a brief moment. + // Visible for testing + static final int MAX_QUEUE_SIZE = 8; + private static final ThreadLocalRecyclerFactory INSTANCE = new ThreadLocalRecyclerFactory(); @@ -64,8 +74,7 @@ private ThreadLocalRecycler( this.supplier = supplier; this.lazyCleaner = lazyCleaner; this.eagerCleaner = eagerCleaner; - // allow for some reasonable level of recursive calls before we stop caring to reuse things - this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(8)); + this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(MAX_QUEUE_SIZE)); } @Override From 815c22c7c29391fb018a880f6ef2f5cd73b87fb0 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:48:10 -0600 Subject: [PATCH 14/39] Use constant for reusable messages Signed-off-by: Matt Sicker --- .../java/org/apache/logging/log4j/core/layout/GelfLayout.java | 4 ++-- .../java/org/apache/logging/log4j/core/util/Constants.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 d3d4ba7a8a8..0e75033498d 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 @@ -487,9 +487,9 @@ private GelfLayout(final Configuration config, final String host, final KeyValue this.mapWriter = new FieldWriter(mapChecker, mapPrefix); this.layout = patternLayout; stacktraceRecycler = recyclerFactory.create( - () -> new StringBuilderWriter(2048), + () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE), writer -> writer.getBuilder().setLength(0), - writer -> StringBuilders.trimToMaxSize(writer.getBuilder(), 2048) + writer -> StringBuilders.trimToMaxSize(writer.getBuilder(), MAX_STRING_BUILDER_SIZE) ); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java index 62fc657e368..578da7717d4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java @@ -112,7 +112,7 @@ private static boolean isJndiEnabled(final String subKey) { * Maximum size of the StringBuilders used in RingBuffer LogEvents to store the contents of reusable Messages. * After a large message has been delivered to the appenders, the StringBuilder is trimmed to this size. *

- * The default value is {@value}, which allows the StringBuilder to resize three times from its initial size. + * The default value is 518, which allows the StringBuilder to resize three times from its initial size. * Users can override with system property "log4j.maxReusableMsgSize". *

* @since 2.6 From 583bf6bf5363202778c8515620b8583ecaabfe83 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 15:54:42 -0600 Subject: [PATCH 15/39] Simplify LogEventFactory lookup Signed-off-by: Matt Sicker --- .../log4j/core/layout/AbstractStringLayout.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 028d9f35609..29fe8555f9b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java @@ -23,7 +23,6 @@ import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.util.Constants; @@ -228,10 +227,6 @@ public Serializer getHeaderSerializer() { return headerSerializer; } - private LogEventFactory getLogEventFactory() { - return configuration != null ? configuration.getLogEventFactory() : DefaultLogEventFactory.newInstance(); - } - /** * Returns a {@code Encoder} that this Layout implementation can use for encoding log events. * @@ -270,9 +265,10 @@ protected String serializeToString(final Serializer serializer) { if (serializer == null) { return null; } - final LoggerConfig rootLogger = getConfiguration().getRootLogger(); + final LoggerConfig rootLogger = configuration.getRootLogger(); + final LogEventFactory logEventFactory = configuration.getLogEventFactory(); // Using "" for the FQCN, does it matter? - final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, + final LogEvent logEvent = logEventFactory.createEvent(rootLogger.getName(), null, Strings.EMPTY, rootLogger.getLevel(), null, null, null); return serializer.toSerializable(logEvent); } From bcc1ee102b0ccc38962887fc83b44297b28e88a7 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 16:04:29 -0600 Subject: [PATCH 16/39] Add missing license header Signed-off-by: Matt Sicker --- docs/2.17.0-interpolation.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/2.17.0-interpolation.md b/docs/2.17.0-interpolation.md index 76c496da3f8..ae43e0f6e38 100644 --- a/docs/2.17.0-interpolation.md +++ b/docs/2.17.0-interpolation.md @@ -1,3 +1,22 @@ + + I'd like to go into detail on some of the changes in 2.17.0, why they're so important, and how they relate to both [CVE-2021-45046](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046) and [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105). The substitution of untrusted log data allowed access to code that was never meant to be exposed. Lookups should be triggered only by configuration and the logging framework (including custom layout/appender/etc plugins). Not by user-provided inputs. @@ -153,4 +172,4 @@ The fix in this case is to inline the `fileName` property value into the `Rollin -``` \ No newline at end of file +``` From 86c6ec4b71c005118f630873c20f62997e8f6aec Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Sun, 22 Jan 2023 17:46:06 -0600 Subject: [PATCH 17/39] Use Recycler API for LogBuilder This simplifies DefaultLogBuilder as reuse is now handled by the recycler. This also adds a place to store a default RecyclerFactory in LoggingSystem. Signed-off-by: Matt Sicker --- .../log4j/internal/DefaultLogBuilder.java | 42 ++++++------------- .../log4j/message/ReusableMessageFactory.java | 4 +- .../message/ReusableParameterizedMessage.java | 4 +- .../logging/log4j/spi/AbstractLogger.java | 33 +++------------ .../logging/log4j/spi/LoggingSystem.java | 9 ++++ .../log4j/core/impl/DefaultBundle.java | 4 +- .../core/layout/AbstractStringLayout.java | 6 +-- .../logging/log4j/core/layout/GelfLayout.java | 4 +- .../log4j/core/layout/PatternLayout.java | 4 +- 9 files changed, 41 insertions(+), 69 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java index cd472c43e3e..0b7f3d65ee5 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java @@ -24,6 +24,7 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.InternalApi; import org.apache.logging.log4j.util.LambdaUtil; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; @@ -33,6 +34,7 @@ /** * Collects data for a log event and then logs it. This class should be considered private. */ +@InternalApi public class DefaultLogBuilder implements BridgeAware, LogBuilder { private static final String FQCN = DefaultLogBuilder.class.getName(); @@ -44,20 +46,11 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { private Marker marker; private Throwable throwable; private StackTraceElement location; - private volatile boolean inUse; private final long threadId; private String fqcn = FQCN; - public DefaultLogBuilder(final Logger logger, final Level level) { - this.logger = logger; - this.level = level; - this.threadId = Thread.currentThread().getId(); - this.inUse = true; - } - public DefaultLogBuilder(final Logger logger) { this.logger = logger; - this.inUse = false; this.threadId = Thread.currentThread().getId(); } @@ -66,44 +59,36 @@ public void setEntryPoint(String fqcn) { this.fqcn = fqcn; } - /** - * This method should be considered internal. It is used to reset the LogBuilder for a new log message. - * @param level The logging level for this event. - * @return This LogBuilder instance. - */ - public LogBuilder reset(final Level level) { - this.inUse = true; + @InternalApi + public LogBuilder atLevel(final Level level) { this.level = level; - this.marker = null; - this.throwable = null; - this.location = null; return this; } + @Override public LogBuilder withMarker(final Marker marker) { this.marker = marker; return this; } + @Override public LogBuilder withThrowable(final Throwable throwable) { this.throwable = throwable; return this; } + @Override public LogBuilder withLocation() { location = StackLocatorUtil.getStackTraceElement(2); return this; } + @Override public LogBuilder withLocation(final StackTraceElement location) { this.location = location; return this; } - public boolean isInUse() { - return inUse; - } - @Override public void log(final Message message) { if (isValid()) { @@ -246,16 +231,15 @@ private void logMessage(final Message message) { try { logger.logMessage(level, marker, fqcn, location, message, throwable); } finally { - inUse = false; + // recycle self + this.level = null; + this.marker = null; + this.throwable = null; + this.location = null; } } private boolean isValid() { - if (!inUse) { - LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", - StackLocatorUtil.getCallerClass(2)); - return false ; - } if (this.threadId != Thread.currentThread().getId()) { LOGGER.warn("LogBuilder can only be used on the owning thread. {}", StackLocatorUtil.getCallerClass(2)); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index 7177712a371..4e494dd82ea 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java @@ -16,8 +16,8 @@ */ 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.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -48,7 +48,7 @@ public final class ReusableMessageFactory implements MessageFactory { * Constructs a message factory using the default {@link RecyclerFactory}. */ public ReusableMessageFactory() { - this(RecyclerFactories.getDefault()); + this(LoggingSystem.getRecyclerFactory()); } public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { 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 513e952e53e..9ec59f6b67c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java @@ -18,8 +18,8 @@ import java.util.Arrays; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -52,7 +52,7 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV * Creates a reusable message. */ public ReusableParameterizedMessage() { - this(RecyclerFactories.getDefault()); + this(LoggingSystem.getRecyclerFactory()); } public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { 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 160b8769c6a..6a6d4368a94 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java @@ -26,11 +26,9 @@ import org.apache.logging.log4j.message.FlowMessageFactory; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.ReusableMessageFactory; import org.apache.logging.log4j.message.StringFormattedMessage; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Cast; -import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.LambdaUtil; import org.apache.logging.log4j.util.MessageSupplier; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -84,7 +82,8 @@ public abstract class AbstractLogger implements ExtendedLogger { private final MessageFactory messageFactory; private final FlowMessageFactory flowMessageFactory; private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031 - protected final transient ThreadLocal logBuilder; + protected final Recycler recycler = LoggingSystem.getRecyclerFactory() + .create(() -> new DefaultLogBuilder(this)); /** @@ -95,7 +94,6 @@ public AbstractLogger() { this.name = canonicalName != null ? canonicalName : getClass().getName(); this.messageFactory = LoggingSystem.getMessageFactory(); this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); - this.logBuilder = new LocalLogBuilder(this); } /** @@ -117,7 +115,6 @@ public AbstractLogger(final String name, final MessageFactory messageFactory) { this.name = name; this.messageFactory = messageFactory == null ? LoggingSystem.getMessageFactory() : messageFactory; this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); - this.logBuilder = new LocalLogBuilder(this); } /** @@ -2742,11 +2739,8 @@ public LogBuilder atFatal() { */ @Override public LogBuilder always() { - final DefaultLogBuilder builder = logBuilder.get(); - if (builder.isInUse()) { - return new DefaultLogBuilder(this); - } - return builder.reset(Level.OFF); + final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); + return builder.atLevel(Level.OFF); } /** @@ -2757,26 +2751,11 @@ public LogBuilder always() { @Override public LogBuilder atLevel(final Level level) { if (isEnabled(level)) { - return (getLogBuilder(level).reset(level)); + final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); + return builder.atLevel(level); } else { return LogBuilder.NOOP; } } - private DefaultLogBuilder getLogBuilder(final Level level) { - final DefaultLogBuilder builder = logBuilder.get(); - return Constants.isThreadLocalsEnabled() && !builder.isInUse() ? builder : new DefaultLogBuilder(this, level); - } - - private static class LocalLogBuilder extends ThreadLocal { - private final AbstractLogger logger; - LocalLogBuilder(final AbstractLogger logger) { - this.logger = logger; - } - - @Override - protected DefaultLogBuilder initialValue() { - return new DefaultLogBuilder(logger); - } - } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java index 3f7981fc629..d3331a67723 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java @@ -99,6 +99,7 @@ public class LoggingSystem { () -> getProvider().createContextMap(environment)); private final Lazy> threadContextStackFactoryLazy = environmentLazy.map(environment -> () -> getProvider().createContextStack(environment)); + private final Lazy recyclerFactoryLazy = Lazy.relaxed(RecyclerFactories::getDefault); /** * Acquires a lock on the initialization of locating a logging system provider. This lock should be @@ -178,6 +179,10 @@ public void setThreadContextStackFactory(final Supplier thre threadContextStackFactoryLazy.set(threadContextStackFactory); } + public void setRecyclerFactory(final RecyclerFactory factory) { + recyclerFactoryLazy.set(factory); + } + /** * Gets the LoggingSystem instance. */ @@ -215,6 +220,10 @@ public static ThreadContextStack createContextStack() { return getInstance().threadContextStackFactoryLazy.value().get(); } + public static RecyclerFactory getRecyclerFactory() { + return getInstance().recyclerFactoryLazy.value(); + } + private static List loadDefaultProviders() { return ServiceRegistry.getInstance() .getServices(Provider.class, MethodHandles.lookup(), provider -> validVersion(provider.getVersions())); diff --git a/log4j-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 1644be057d8..094cdfb679c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java @@ -53,8 +53,8 @@ import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.spi.CopyOnWrite; import org.apache.logging.log4j.spi.DefaultThreadContextMap; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; -import org.apache.logging.log4j.spi.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; @@ -92,7 +92,7 @@ public DefaultBundle(final Injector injector, final PropertyEnvironment properti @SingletonFactory public RecyclerFactory defaultRecyclerFactory() { - return RecyclerFactories.getDefault(); + return LoggingSystem.getRecyclerFactory(); } @ConditionalOnProperty(name = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME) 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 29fe8555f9b..da6e0629072 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java @@ -29,8 +29,8 @@ import org.apache.logging.log4j.core.util.StringEncoder; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.StringBuilders; @@ -159,7 +159,7 @@ protected AbstractStringLayout(final Charset aCharset, final byte[] header, fina this.footerSerializer = null; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; - recyclerFactory = RecyclerFactories.getDefault(); + recyclerFactory = LoggingSystem.getRecyclerFactory(); recycler = createRecycler(recyclerFactory); } @@ -178,7 +178,7 @@ protected AbstractStringLayout(final Configuration config, final Charset aCharse this.footerSerializer = footerSerializer; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; - recyclerFactory = config != null ? config.getRecyclerFactory() : RecyclerFactories.getDefault(); + recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); recycler = createRecycler(recyclerFactory); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java index 0e75033498d..bd0d0f6bcb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java @@ -51,8 +51,8 @@ import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.StringBuilderFormattable; @@ -207,7 +207,7 @@ public GelfLayout build() { .build(); } if (recyclerFactory == null) { - recyclerFactory = config != null ? config.getRecyclerFactory() : RecyclerFactories.getDefault(); + recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); } return new GelfLayout(config, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter, 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 8dd96b506b0..2f365b47a4b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java @@ -38,8 +38,8 @@ import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactories; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.PropertyEnvironment; @@ -386,7 +386,7 @@ public Serializer build() { } final RecyclerFactory recyclerFactory = configuration != null ? configuration.getRecyclerFactory() - : RecyclerFactories.getDefault(); + : LoggingSystem.getRecyclerFactory(); final Recycler recycler = createRecycler(recyclerFactory); if (patternSelector == null) { try { From d96b37020ba6cd52fe1283ae8864a93685e943f9 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 13 Jan 2023 06:15:27 +0100 Subject: [PATCH 18/39] [LOG4J2-3647] Add global filter support to `LogBuilder` `LogBuilder` suppliers in the `Logger` class always return a no-op builder if the log message level is higher than the configured level. This way global filters are always skipped. This patch always returns a functional builder if we detect the presence of a global filter. --- .../log4j/internal/DefaultLogBuilder.java | 21 +- .../logging/log4j/spi/AbstractLogger.java | 43 ++-- .../org/apache/logging/log4j/core/Logger.java | 10 + .../apache/logging/slf4j/SLF4JLogBuilder.java | 229 +++++------------- .../org/apache/logging/slf4j/SLF4JLogger.java | 32 ++- 5 files changed, 126 insertions(+), 209 deletions(-) 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 184f0fc3c3e..2690c6ee729 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 @@ -43,7 +43,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { private static final Logger LOGGER = StatusLogger.getLogger(); private static final Message EMPTY_MESSAGE = new SimpleMessage(Strings.EMPTY); - private final ExtendedLogger logger; + private ExtendedLogger logger; private Level level; private Marker marker; private Throwable throwable; @@ -51,19 +51,32 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { private final long threadId; private String fqcn = FQCN; - public DefaultLogBuilder(final ExtendedLogger logger) { + public DefaultLogBuilder(final ExtendedLogger logger, final Level level) { this.logger = logger; + this.level = level; this.threadId = Thread.currentThread().getId(); } + public DefaultLogBuilder() { + this(null, null); + } + @Override public void setEntryPoint(String fqcn) { this.fqcn = fqcn; } - @InternalApi - public LogBuilder atLevel(final Level level) { + /** + * This method should be considered internal. It is used to reset the LogBuilder for a new log message. + * @param level The logging level for this event. + * @return This LogBuilder instance. + */ + public LogBuilder reset(ExtendedLogger logger, Level level) { + this.logger = logger; this.level = level; + this.marker = null; + this.throwable = null; + this.location = null; return this; } 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 6a6d4368a94..c9ed863ae0e 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,24 +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.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.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. @@ -83,8 +70,7 @@ public abstract class AbstractLogger implements ExtendedLogger { private final FlowMessageFactory flowMessageFactory; private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031 protected final Recycler recycler = LoggingSystem.getRecyclerFactory() - .create(() -> new DefaultLogBuilder(this)); - + .create(() -> new DefaultLogBuilder(this, null)); /** * Creates a new logger named after this class (or subclass). @@ -1974,7 +1960,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()) - messageFactory.recycle(msg); + ReusableMessageFactory.release(msg); } } @@ -2739,8 +2725,7 @@ public LogBuilder atFatal() { */ @Override public LogBuilder always() { - final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); - return builder.atLevel(Level.OFF); + return getLogBuilder(Level.OFF); } /** @@ -2751,11 +2736,19 @@ public LogBuilder always() { @Override public LogBuilder atLevel(final Level level) { if (isEnabled(level)) { - final DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); - return builder.atLevel(level); - } else { - return LogBuilder.NOOP; + return getLogBuilder(level); } + return LogBuilder.NOOP; + } + + /** + * Returns a log builder that logs at the specified level. + * + * @since 2.20.0 + */ + protected LogBuilder getLogBuilder(Level level) { + DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); + return builder.reset(this, level); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java index 8578bbd92ea..9da3d45454b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java @@ -346,6 +346,16 @@ public void setAdditive(final boolean additive) { privateConfig.config.setLoggerAdditive(this, additive); } + @Override + public LogBuilder atLevel(Level level) { + // A global filter might accept messages less specific than level. + // Therefore we return always a functional builder. + if (privateConfig.hasFilter()) { + return getLogBuilder(level); + } + return super.atLevel(level); + } + /** * Associates this Logger with a new Configuration. This method is not * exposed through the public API. diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java index a910694d683..947391739f6 100644 --- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java +++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java @@ -17,233 +17,112 @@ package org.apache.logging.slf4j; 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.internal.DefaultLogBuilder; 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.Supplier; -public class SLF4JLogBuilder implements LogBuilder { +public class SLF4JLogBuilder extends DefaultLogBuilder { - private static Message EMPTY_MESSAGE = new SimpleMessage(""); - private static final String FQCN = SLF4JLogBuilder.class.getName(); - private static final Logger LOGGER = StatusLogger.getLogger(); - - private ExtendedLogger logger; - private Level level; - private Marker marker; - private Throwable throwable; - private volatile boolean inUse; - private final long threadId; - - public SLF4JLogBuilder(SLF4JLogger logger, Level level) { - this.logger = logger; - this.level = level; - this.threadId = Thread.currentThread().getId(); - this.inUse = level != null; + public SLF4JLogBuilder(ExtendedLogger logger, Level level) { + super(logger, level); } public SLF4JLogBuilder() { - this(null, null); - } - - public LogBuilder reset(SLF4JLogger logger, Level level) { - this.logger = logger; - this.level = level; - this.marker = null; - this.throwable = null; - this.inUse = true; - return this; - } - - public boolean isInUse() { - return this.inUse; - } - - private boolean isValid() { - if (!inUse) { - LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", StackLocatorUtil.getCallerClass(2)); - return false; - } - if (this.threadId != Thread.currentThread().getId()) { - LOGGER.warn("LogBuilder can only be used on the owning thread. {}", StackLocatorUtil.getCallerClass(2)); - return false; - } - return true; - } - - private void logMessage(Message message) { - try { - logger.logMessage(FQCN, level, marker, message, throwable); - } finally { - inUse = false; - } - } - - @Override - public LogBuilder withMarker(Marker marker) { - this.marker = marker; - return this; - } - - @Override - public LogBuilder withThrowable(Throwable throwable) { - this.throwable = throwable; - return this; - } - - @Override - public LogBuilder withLocation() { - LOGGER.info("Call to withLocation() ignored since SLF4J does not support setting location information."); - return this; - } - - @Override - public LogBuilder withLocation(StackTraceElement location) { - return withLocation(); - } - - @Override - public void log(CharSequence message) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message)); - } - } - - @Override - public void log(String message) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message)); - } - } - - @Override - public void log(String message, Object... params) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, params)); - } + super(); } @Override - public void log(String message, Supplier... params) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params))); - } - } - - @Override - public void log(Message message) { - if (isValid()) { - logMessage(message); - } + protected boolean isEnabled(Message message) { + // SLF4J will check again later + return true; } @Override - public void log(Supplier messageSupplier) { - if (isValid()) { - logMessage(messageSupplier.get()); - } + protected boolean isEnabled(CharSequence message) { + // SLF4J will check again later + return true; } @Override - public Message logAndGet(Supplier messageSupplier) { - Message message = null; - if (isValid()) { - logMessage(message = messageSupplier.get()); - } - return message; + protected boolean isEnabled(String message) { + // SLF4J will check again later + return true; } @Override - public void log(Object message) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message)); - } + protected boolean isEnabled(String message, Object... params) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0)); - } + protected boolean isEnabled(Object message) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); - } + protected boolean isEnabled(String message, Object p0) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); - } + protected boolean isEnabled(String message, Object p0, Object p1) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, + Object p6) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, + Object p6, Object p7) { + // SLF4J will check again later + return true; } @Override - public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9) { - if (isValid()) { - logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, + Object p6, Object p7, Object p8) { + // SLF4J will check again later + return true; } @Override - public void log() { - if (isValid()) { - logMessage(EMPTY_MESSAGE); - } + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, + Object p6, Object p7, Object p8, Object p9) { + // SLF4J will check again later + return true; } } 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 c040666555a..4034b1ba3f3 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,6 +23,9 @@ 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.spi.LoggingSystem; +import org.apache.logging.log4j.spi.Recycler; +import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; import org.slf4j.spi.LocationAwareLogger; @@ -31,6 +34,15 @@ */ public class SLF4JLogger extends AbstractLogger { + /** + * 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 Recycler logBuilderRecycler = + LoggingSystem.getRecyclerFactory().create(SLF4JLogBuilder::new); + private final org.slf4j.Logger logger; private final LocationAwareLogger locationAwareLogger; @@ -259,11 +271,6 @@ public void logMessage(final String fqcn, final Level level, final Marker marker } } - @Override - public LogBuilder always() { - return atLevel(Level.OFF); - } - @Override public LogBuilder atTrace() { return atLevel(Level.TRACE); @@ -294,4 +301,19 @@ public LogBuilder atFatal() { return atLevel(Level.TRACE); } + @Override + protected LogBuilder getLogBuilder(Level level) { + SLF4JLogBuilder builder = logBuilderRecycler.acquire(); + return builder.reset(this, level); + } + + @Override + public LogBuilder atLevel(Level level) { + // TODO: wrap SLF4J 2.x LoggingEventBuilder + if (LAZY_LEVEL_CHECK) { + return getLogBuilder(level); + } + return super.atLevel(level); + } + } From 3ab7a6971ab468232baeedb13ac5929d3af7281f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 20 Mar 2023 13:07:43 +0100 Subject: [PATCH 19/39] Move `RecyclerFactory` type converter to `log4j-plugins` --- .../json/util/RecyclerFactoryConverter.java | 37 ------------------- .../plugins/convert/TypeConverterFactory.java | 3 ++ 2 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java deleted file mode 100644 index 4d596872085..00000000000 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java +++ /dev/null @@ -1,37 +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 org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.plugins.convert.TypeConverter; -import org.apache.logging.log4j.plugins.convert.TypeConverters; -import org.apache.logging.log4j.spi.RecyclerFactories; -import org.apache.logging.log4j.spi.RecyclerFactory; - -/** - * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter. - */ -@TypeConverters -@Plugin -public final class RecyclerFactoryConverter implements TypeConverter { - - @Override - public RecyclerFactory convert(final String recyclerFactorySpec) { - return RecyclerFactories.ofSpec(recyclerFactorySpec); - } - -} 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 83322582686..2d800ab728b 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.EnglishEnums; @@ -57,6 +59,7 @@ public TypeConverterFactory(@TypeConverters List> typeConverter 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); From 6d2a5a2d0fa7506b8224fffef17c61a7b416e7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 20 Mar 2023 13:30:18 +0100 Subject: [PATCH 20/39] Fix code typo in `AbstractLogger` --- .../main/java/org/apache/logging/log4j/spi/AbstractLogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c9ed863ae0e..043da09bc6d 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 @@ -1960,7 +1960,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); } } From 4834eaad2ae3f63ff898e6ceb529fd031bafa727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 20 Mar 2023 19:42:24 +0100 Subject: [PATCH 21/39] Make `HierarchicalCollections` thread safe --- .../log4j/plugins/internal/util/HierarchicalCollections.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java index ad47c274279..dbeb78232d9 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java @@ -19,10 +19,10 @@ import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; class HierarchicalCollections { static HierarchicalMap newRootMap() { @@ -30,7 +30,7 @@ static HierarchicalMap newRootMap() { } private static class RootMap extends AbstractMap implements HierarchicalMap { - private final Map map = new LinkedHashMap<>(); + private final Map map = new ConcurrentHashMap<>(); @Override public Set> entrySet() { From 21abf71034d6f936f25ae126f43d4caddc266632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 20 Mar 2023 22:35:37 +0100 Subject: [PATCH 22/39] Use a single cleaner in recyclers --- .../spi/ThreadLocalRecyclerFactoryTest.java | 12 +++----- .../log4j/message/ReusableMessageFactory.java | 3 -- .../message/ReusableParameterizedMessage.java | 6 ++-- .../log4j/spi/DummyRecyclerFactory.java | 6 ++-- .../log4j/spi/QueueingRecyclerFactory.java | 27 +++++------------ .../logging/log4j/spi/RecyclerFactory.java | 27 ++--------------- .../log4j/spi/ThreadLocalRecyclerFactory.java | 29 +++++-------------- .../core/layout/AbstractStringLayout.java | 6 ++-- .../logging/log4j/core/layout/GelfLayout.java | 7 +++-- .../resolver/StackTraceStringResolver.java | 14 +++++---- 10 files changed, 45 insertions(+), 92 deletions(-) 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 index 14428d20c97..b7ab3393759 100644 --- 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 @@ -43,14 +43,10 @@ private Queue getRecyclerQueue() { @BeforeEach void setUp() { - recycler = ThreadLocalRecyclerFactory.getInstance() - .create(RecyclableObject::new, o -> { - o.using = true; - o.returned = false; - }, o -> { - o.returned = true; - o.using = false; - }); + recycler = ThreadLocalRecyclerFactory.getInstance().create(RecyclableObject::new, object -> { + object.using = true; + object.returned = false; + }); } @ParameterizedTest 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 4e494dd82ea..7410005c0a4 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 @@ -55,15 +55,12 @@ public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { super(); parameterizedMessageRecycler = recyclerFactory.create( ReusableParameterizedMessage::new, - RecyclerFactory.getDefaultCleaner(), ReusableParameterizedMessage::clear); simpleMessageRecycler = recyclerFactory.create( ReusableSimpleMessage::new, - RecyclerFactory.getDefaultCleaner(), ReusableSimpleMessage::clear); objectMessageRecycler = recyclerFactory.create( ReusableObjectMessage::new, - RecyclerFactory.getDefaultCleaner(), ReusableObjectMessage::clear); } 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 9ec59f6b67c..24982934d27 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 @@ -61,8 +61,10 @@ public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length(); return new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2)); }, - buffer -> buffer.setLength(0), - buffer -> StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE) + buffer -> { + StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + buffer.setLength(0); + } ); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index 3ca81b521d7..5f6f6d60192 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -35,10 +35,7 @@ public static DummyRecyclerFactory getInstance() { } @Override - public Recycler create( - final Supplier supplier, - final Consumer lazyCleaner, - final Consumer eagerCleaner) { + public Recycler create(final Supplier supplier, final Consumer cleaner) { return new DummyRecycler<>(supplier); } @@ -59,4 +56,5 @@ public V acquire() { public void release(final V value) {} } + } 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 index 3387ee47803..7579566ef1f 100644 --- 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 @@ -31,12 +31,9 @@ public QueueingRecyclerFactory(final QueueFactory queueFactory) { } @Override - public Recycler create( - final Supplier supplier, - final Consumer lazyCleaner, - final Consumer eagerCleaner) { + public Recycler create(final Supplier supplier, final Consumer cleaner) { final Queue queue = queueFactory.create(); - return new QueueingRecycler<>(supplier, lazyCleaner, eagerCleaner, queue); + return new QueueingRecycler<>(supplier, cleaner, queue); } // Visible for tests. @@ -44,20 +41,16 @@ static class QueueingRecycler implements Recycler { private final Supplier supplier; - private final Consumer lazyCleaner; - - private final Consumer eagerCleaner; + private final Consumer cleaner; private final Queue queue; private QueueingRecycler( final Supplier supplier, - final Consumer lazyCleaner, - final Consumer eagerCleaner, + final Consumer cleaner, final Queue queue) { this.supplier = supplier; - this.lazyCleaner = lazyCleaner; - this.eagerCleaner = eagerCleaner; + this.cleaner = cleaner; this.queue = queue; } @@ -69,19 +62,15 @@ Queue getQueue() { @Override public V acquire() { final V value = queue.poll(); - if (value == null) { - return supplier.get(); - } else { - lazyCleaner.accept(value); - return value; - } + return value != null ? value : supplier.get(); } @Override public void release(final V value) { - eagerCleaner.accept(value); + cleaner.accept(value); queue.offer(value); } } + } 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 index 5bed3016911..90b81327556 100644 --- 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 @@ -39,7 +39,7 @@ public interface RecyclerFactory { * @return a new recycler for V-type instances */ default Recycler create(final Supplier supplier) { - return create(supplier, getDefaultCleaner()); + return create(supplier, ignored -> {}); } /** @@ -54,29 +54,6 @@ default Recycler create(final Supplier supplier) { * @param the recyclable type * @return a new recycler for V-type instances */ - default Recycler create(Supplier supplier, Consumer cleaner) { - return create(supplier, cleaner, getDefaultCleaner()); - } - - /** - * Creates a new recycler using the given functions for providing fresh instances and for cleaning recycled - * instances lazily or eagerly. The lazy cleaner function is invoked on recycled instances before being - * returned by {@link Recycler#acquire()}. The eager cleaner function is invoked on recycled instances - * during {@link Recycler#release(Object)}. - * - * @param supplier function to provide new instances of a recyclable object - * @param lazyCleaner function to invoke to clean a recycled object before being acquired - * @param eagerCleaner function to invoke to clean a recycled object after being released - * @param the recyclable type - * @return a new recycler for V-type instances - */ - Recycler create(Supplier supplier, Consumer lazyCleaner, Consumer eagerCleaner); - - /** - * Creates a default cleaner function that does nothing. - */ - static Consumer getDefaultCleaner() { - return ignored -> {}; - } + 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 index 8a031c1ed98..65a8d1915f7 100644 --- 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 @@ -49,11 +49,8 @@ public static ThreadLocalRecyclerFactory getInstance() { } @Override - public Recycler create( - final Supplier supplier, - final Consumer lazyCleaner, - final Consumer eagerCleaner) { - return new ThreadLocalRecycler<>(supplier, lazyCleaner, eagerCleaner); + public Recycler create(final Supplier supplier, final Consumer cleaner) { + return new ThreadLocalRecycler<>(supplier, cleaner); } // Visible for testing @@ -61,19 +58,13 @@ static class ThreadLocalRecycler implements Recycler { private final Supplier supplier; - private final Consumer lazyCleaner; - - private final Consumer eagerCleaner; + private final Consumer cleaner; private final ThreadLocal> holder; - private ThreadLocalRecycler( - final Supplier supplier, - final Consumer lazyCleaner, - final Consumer eagerCleaner) { + private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner) { this.supplier = supplier; - this.lazyCleaner = lazyCleaner; - this.eagerCleaner = eagerCleaner; + this.cleaner = cleaner; this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(MAX_QUEUE_SIZE)); } @@ -81,17 +72,12 @@ private ThreadLocalRecycler( public V acquire() { final Queue queue = holder.get(); final V value = queue.poll(); - if (value == null) { - return supplier.get(); - } else { - lazyCleaner.accept(value); - return value; - } + return value != null ? value : supplier.get(); } @Override public void release(final V value) { - eagerCleaner.accept(value); + cleaner.accept(value); holder.get().offer(value); } @@ -101,4 +87,5 @@ Queue getQueue() { } } + } 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 da6e0629072..55572b4252d 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 @@ -123,8 +123,10 @@ private static int size(final String property, final int defaultValue) { protected static Recycler createRecycler(final RecyclerFactory recyclerFactory) { return recyclerFactory.create( () -> new StringBuilder(DEFAULT_STRING_BUILDER_SIZE), - stringBuilder -> stringBuilder.setLength(0), - stringBuilder -> StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE) + stringBuilder -> { + StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); + stringBuilder.setLength(0); + } ); } 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 bd0d0f6bcb8..69baf2f181f 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 @@ -488,8 +488,11 @@ private GelfLayout(final Configuration config, final String host, final KeyValue this.layout = patternLayout; stacktraceRecycler = recyclerFactory.create( () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE), - writer -> writer.getBuilder().setLength(0), - writer -> StringBuilders.trimToMaxSize(writer.getBuilder(), MAX_STRING_BUILDER_SIZE) + writer -> { + final StringBuilder stringBuilder = writer.getBuilder(); + StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); + stringBuilder.setLength(0); + } ); } 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 35c2f23ca12..a39a8d6180f 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 @@ -17,6 +17,7 @@ package org.apache.logging.log4j.layout.template.json.resolver; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -90,19 +91,20 @@ public void resolve( final TruncatingBufferedPrintWriter srcWriter = srcWriterRecycler.acquire(); try { throwable.printStackTrace(srcWriter); - final TruncatingBufferedPrintWriter dstWriter = truncate(srcWriter); - jsonWriter.writeString(dstWriter); + truncate(srcWriter, jsonWriter::writeString); } finally { srcWriterRecycler.release(srcWriter); } } - private TruncatingBufferedPrintWriter truncate( - final TruncatingBufferedPrintWriter srcWriter) { + private void truncate( + final TruncatingBufferedPrintWriter srcWriter, + final Consumer effectiveWriterConsumer) { // Short-circuit if truncation is not enabled. if (!truncationEnabled) { - return srcWriter; + effectiveWriterConsumer.accept(srcWriter); + return; } // Allocate temporary buffers and truncate the input. @@ -116,10 +118,10 @@ private TruncatingBufferedPrintWriter truncate( } finally { sequencePointerRecycler.release(sequencePointer); } + effectiveWriterConsumer.accept(dstWriter); } finally { dstWriterRecycler.release(dstWriter); } - return dstWriter; } From baf4a9bcb29b41422237495bcf7c1ab2db459520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 Mar 2023 18:14:19 +0100 Subject: [PATCH 23/39] Merge changes from `main` --- .../logging/log4j/message/MapMessageTest.java | 9 + .../logging/log4j/util/StringBuilders.java | 33 +- .../core/filter/CompositeFilterTest.java | 97 +++--- .../SimpleLiteralPatternConverterTest.java | 93 +++--- .../log4j/core/appender/WriterManager.java | 298 +++++++++--------- .../rolling/action/DeletingVisitor.java | 219 +++++++------ .../appender/rolling/action/PathSorter.java | 53 ++-- .../rolling/action/PathWithAttributes.java | 117 ++++--- .../rolling/action/SortingVisitor.java | 149 +++++---- .../core/async/AsyncLoggerConfigDelegate.java | 131 ++++---- .../BasicAsyncLoggerContextSelector.java | 88 +++--- .../core/config/ReliabilityStrategy.java | 189 ++++++----- ...faultComponentAndConfigurationBuilder.java | 94 +++--- .../core/selector/CoreContextSelectors.java | 62 ++-- .../core/util/CloseShieldOutputStream.java | 118 +++---- .../log4j/core/util/CloseShieldWriter.java | 92 +++--- .../logging/log4j/core/util/IOUtils.java | 260 +++++++-------- .../log4j/jeromq/appender/JeroMqAppender.java | 5 + .../log4j/jeromq/appender/JeroMqManager.java | 16 +- .../jeromq/appender/JeroMqAppenderTest.java | 21 +- .../layout/template/json/TestHelpers.java | 9 +- .../json/resolver/MarkerResolverTest.java | 94 ++++++ .../json/resolver/MarkerResolver.java | 41 ++- .../util/HierarchicalCollections.java | 7 +- log4j-to-slf4j/pom.xml | 2 + pom.xml | 9 +- .../1232_log4j-to-sfl4j-2-OSGiMetadata.xml | 29 ++ .../.2.x.x/1366_fix_java_sql_date.xml | 28 ++ .../manual/json-template-layout.adoc.vm | 54 ++++ 29 files changed, 1356 insertions(+), 1061 deletions(-) create mode 100644 log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverTest.java create mode 100644 src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml create mode 100644 src/changelog/.2.x.x/1366_fix_java_sql_date.xml diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java index 106cce2c975..b4e67f3bc22 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.message; import java.math.BigDecimal; +import java.sql.Date; import java.sql.Time; import java.util.Arrays; import java.util.Collections; @@ -320,6 +321,14 @@ public void testTime() throws Exception { message.getFormattedMessage(), "Incorrect time format"); } + @Test + public void testDate() { + final Date date = new Date(System.currentTimeMillis()); + final ObjectMapMessage message = new ObjectMapMessage().with("date", date); + assertEquals("date=\"" + date + "\"", + message.getFormattedMessage(), "Incorrect date format"); + } + private static final class FormattableTestType implements StringBuilderFormattable { @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java index d0c931d039b..cc81a66105c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.util; -import java.lang.reflect.Array; -import java.util.Collections; import java.util.Map.Entry; import static java.lang.Character.toLowerCase; @@ -28,18 +26,28 @@ @InternalApi public final class StringBuilders { - private static final Object timeClass; + private static final Object timeObj; + private static final Object dateObj; static { + Class clazz; Object obj; + final long current = System.currentTimeMillis(); try { - Class clazz = Class.forName("java.sql.Time"); - long current = System.currentTimeMillis(); + clazz = Class.forName("java.sql.Time"); obj = clazz.getDeclaredConstructor(Long.TYPE).newInstance(current); } catch(Exception ex) { obj = null; } - timeClass = obj; + timeObj = obj; + + try { + clazz = Class.forName("java.sql.Date"); + obj = clazz.getDeclaredConstructor(Long.TYPE).newInstance(current); + } catch(Exception ex) { + obj = null; + } + dateObj = obj; } private StringBuilders() { @@ -135,7 +143,7 @@ public static boolean appendSpecificTypes(final StringBuilder stringBuilder, fin stringBuilder.append(((Float) obj).floatValue()); } else if (obj instanceof Byte) { stringBuilder.append(((Byte) obj).byteValue()); - } else if (isTime(obj) || obj instanceof java.time.temporal.Temporal) { + } else if (isTime(obj) || isDate(obj) || obj instanceof java.time.temporal.Temporal) { stringBuilder.append(obj); } else { return false; @@ -144,10 +152,17 @@ public static boolean appendSpecificTypes(final StringBuilder stringBuilder, fin } /* - Check to see if obj is an instance of java.sql.time without requiring the java.sql module. + Check to see if obj is an instance of java.sql.Time without requiring the java.sql module. */ private static boolean isTime(final Object obj) { - return obj.getClass().isInstance(timeClass); + return obj.getClass().isInstance(timeObj); + } + + /* + Check to see if obj is an instance of java.sql.Date without requiring the java.sql module. + */ + private static boolean isDate(final Object obj) { + return obj.getClass().isInstance(dateObj); } /** diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java index 0a5698b6078..14d7f16d76c 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java @@ -1,49 +1,48 @@ -/* 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.filter; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Filter.Result; -import org.junit.jupiter.api.Test; - -public class CompositeFilterTest { - - @Test - public void testConcatenation() { - final Filter a = DenyAllFilter.newBuilder().setOnMatch(Result.ACCEPT).build(); - final Filter b = DenyAllFilter.newBuilder().setOnMatch(Result.NEUTRAL).build(); - final Filter c = DenyAllFilter.newBuilder().setOnMatch(Result.DENY).build(); - // The three values need to be distinguishable - assertNotEquals(a, b); - assertNotEquals(a, c); - assertNotEquals(b, c); - final Filter[] expected = new Filter[] {a, b, c}; - final CompositeFilter singleA = CompositeFilter.createFilters(new Filter[] {a}); - final CompositeFilter singleB = CompositeFilter.createFilters(new Filter[] {b}); - final CompositeFilter singleC = CompositeFilter.createFilters(new Filter[] {c}); - // Concatenating one at a time - final CompositeFilter concat1 = singleA.addFilter(b).addFilter(c); - assertArrayEquals(expected, concat1.getFiltersArray()); - // In reverse order - final CompositeFilter concat2 = singleA.addFilter(singleB.addFilter(singleC)); - assertArrayEquals(expected, concat2.getFiltersArray()); - } -} +/* + * 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.filter; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class CompositeFilterTest { + + @Test + public void testConcatenation() { + final Filter a = DenyAllFilter.newBuilder().setOnMatch(Result.ACCEPT).build(); + final Filter b = DenyAllFilter.newBuilder().setOnMatch(Result.NEUTRAL).build(); + final Filter c = DenyAllFilter.newBuilder().setOnMatch(Result.DENY).build(); + // The three values need to be distinguishable + assertNotEquals(a, b); + assertNotEquals(a, c); + assertNotEquals(b, c); + final Filter[] expected = new Filter[] {a, b, c}; + final CompositeFilter singleA = CompositeFilter.createFilters(new Filter[] {a}); + final CompositeFilter singleB = CompositeFilter.createFilters(new Filter[] {b}); + final CompositeFilter singleC = CompositeFilter.createFilters(new Filter[] {c}); + // Concatenating one at a time + final CompositeFilter concat1 = singleA.addFilter(b).addFilter(c); + assertArrayEquals(expected, concat1.getFiltersArray()); + // In reverse order + final CompositeFilter concat2 = singleA.addFilter(singleB.addFilter(singleC)); + assertArrayEquals(expected, concat2.getFiltersArray()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java index ba6b4024471..4b46e42b326 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java @@ -1,47 +1,46 @@ -/* - * 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.pattern; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class SimpleLiteralPatternConverterTest { - - @Test - public void testConvertBackslashes() { - String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; - LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, true); - String actual = literal(converter); - assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", actual); - } - - @Test - public void testDontConvertBackslashes() { - String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; - LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, false); - String actual = literal(converter); - assertEquals(literal, actual); - } - - private static String literal(LogEventPatternConverter converter) { - StringBuilder buffer = new StringBuilder(); - converter.format(null, buffer); - return buffer.toString(); - } -} +/* + * 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.pattern; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SimpleLiteralPatternConverterTest { + + @Test + public void testConvertBackslashes() { + String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, true); + String actual = literal(converter); + assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", actual); + } + + @Test + public void testDontConvertBackslashes() { + String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, false); + String actual = literal(converter); + assertEquals(literal, actual); + } + + private static String literal(LogEventPatternConverter converter) { + StringBuilder buffer = new StringBuilder(); + converter.format(null, buffer); + return buffer.toString(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java index bf76e47b25a..32c024fb0fa 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java @@ -1,149 +1,149 @@ -/* - * 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.appender; - -import java.io.IOException; -import java.io.Writer; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.StringLayout; - -/** - * Manages a Writer so that it can be shared by multiple Appenders and will - * allow appenders to reconfigure without requiring a new writer. - */ -public class WriterManager extends AbstractManager { - - /** - * Creates a Manager. - * - * @param name The name of the stream to manage. - * @param data The data to pass to the Manager. - * @param factory The factory to use to create the Manager. - * @param The type of the WriterManager. - * @return A WriterManager. - */ - public static WriterManager getManager(final String name, final T data, - final ManagerFactory factory) { - return AbstractManager.getManager(name, factory, data); - } - protected final StringLayout layout; - - private volatile Writer writer; - - public WriterManager(final Writer writer, final String streamName, final StringLayout layout, - final boolean writeHeader) { - super(null, streamName); - this.writer = writer; - this.layout = layout; - if (writeHeader && layout != null) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - this.writer.write(new String(header, layout.getCharset())); - } catch (final IOException e) { - logError("Unable to write header", e); - } - } - } - } - - protected synchronized void closeWriter() { - final Writer w = writer; // access volatile field only once per method - try { - w.close(); - } catch (final IOException ex) { - logError("Unable to close stream", ex); - } - } - - /** - * Flushes any buffers. - */ - public synchronized void flush() { - try { - writer.flush(); - } catch (final IOException ex) { - final String msg = "Error flushing stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - protected Writer getWriter() { - return writer; - } - - /** - * Returns the status of the stream. - * @return true if the stream is open, false if it is not. - */ - public boolean isOpen() { - return getCount() > 0; - } - - /** - * Default hook to write footer during close. - */ - @Override - public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - writeFooter(); - closeWriter(); - return true; - } - - protected void setWriter(final Writer writer) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - writer.write(new String(header, layout.getCharset())); - this.writer = writer; // only update field if writer.write() succeeded - } catch (final IOException ioe) { - logError("Unable to write header", ioe); - } - } else { - this.writer = writer; - } - } - - /** - * Some output streams synchronize writes while others do not. Synchronizing here insures that - * log events won't be intertwined. - * @param str the string to write - * @throws AppenderLoggingException if an error occurs. - */ - protected synchronized void write(final String str) { - try { - writer.write(str); - } catch (final IOException ex) { - final String msg = "Error writing to stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - /** - * Writes the footer. - */ - protected void writeFooter() { - if (layout == null) { - return; - } - final byte[] footer = layout.getFooter(); - if (footer != null && footer.length > 0) { - write(new String(footer, layout.getCharset())); - } - } -} +/* + * 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.appender; + +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.core.StringLayout; + +/** + * Manages a Writer so that it can be shared by multiple Appenders and will + * allow appenders to reconfigure without requiring a new writer. + */ +public class WriterManager extends AbstractManager { + + /** + * Creates a Manager. + * + * @param name The name of the stream to manage. + * @param data The data to pass to the Manager. + * @param factory The factory to use to create the Manager. + * @param The type of the WriterManager. + * @return A WriterManager. + */ + public static WriterManager getManager(final String name, final T data, + final ManagerFactory factory) { + return AbstractManager.getManager(name, factory, data); + } + protected final StringLayout layout; + + private volatile Writer writer; + + public WriterManager(final Writer writer, final String streamName, final StringLayout layout, + final boolean writeHeader) { + super(null, streamName); + this.writer = writer; + this.layout = layout; + if (writeHeader && layout != null) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + this.writer.write(new String(header, layout.getCharset())); + } catch (final IOException e) { + logError("Unable to write header", e); + } + } + } + } + + protected synchronized void closeWriter() { + final Writer w = writer; // access volatile field only once per method + try { + w.close(); + } catch (final IOException ex) { + logError("Unable to close stream", ex); + } + } + + /** + * Flushes any buffers. + */ + public synchronized void flush() { + try { + writer.flush(); + } catch (final IOException ex) { + final String msg = "Error flushing stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + protected Writer getWriter() { + return writer; + } + + /** + * Returns the status of the stream. + * @return true if the stream is open, false if it is not. + */ + public boolean isOpen() { + return getCount() > 0; + } + + /** + * Default hook to write footer during close. + */ + @Override + public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + writeFooter(); + closeWriter(); + return true; + } + + protected void setWriter(final Writer writer) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + writer.write(new String(header, layout.getCharset())); + this.writer = writer; // only update field if writer.write() succeeded + } catch (final IOException ioe) { + logError("Unable to write header", ioe); + } + } else { + this.writer = writer; + } + } + + /** + * Some output streams synchronize writes while others do not. Synchronizing here insures that + * log events won't be intertwined. + * @param str the string to write + * @throws AppenderLoggingException if an error occurs. + */ + protected synchronized void write(final String str) { + try { + writer.write(str); + } catch (final IOException ex) { + final String msg = "Error writing to stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + /** + * Writes the footer. + */ + protected void writeFooter() { + if (layout == null) { + return; + } + final byte[] footer = layout.getFooter(); + if (footer != null && footer.length > 0) { + write(new String(footer, layout.getCharset())); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java index 69a59a7d8cd..e433731cf25 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java @@ -1,110 +1,109 @@ -/* - * 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.appender.rolling.action; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.List; -import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored. - */ -public class DeletingVisitor extends SimpleFileVisitor { - private static final Logger LOGGER = StatusLogger.getLogger(); - - private final Path basePath; - private final boolean testMode; - private final List pathConditions; - - /** - * Constructs a new DeletingVisitor. - * - * @param basePath used to relativize paths - * @param pathConditions objects that need to confirm whether a file can be deleted - * @param testMode if true, files are not deleted but instead a message is printed to the status logger - * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. - */ - public DeletingVisitor(final Path basePath, final List pathConditions, - final boolean testMode) { - this.testMode = testMode; - this.basePath = Objects.requireNonNull(basePath, "basePath"); - this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions"); - for (final PathCondition condition : pathConditions) { - condition.beforeFileTreeWalk(); - } - } - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - for (final PathCondition pathFilter : pathConditions) { - final Path relative = basePath.relativize(file); - if (!pathFilter.accept(basePath, relative, attrs)) { - LOGGER.trace("Not deleting base={}, relative={}", basePath, relative); - return FileVisitResult.CONTINUE; - } - } - if (isTestMode()) { - LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file); - } else { - delete(file); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(final Path file, final IOException ioException) throws IOException { - // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from - // failed attempts to load file attributes. - if (ioException instanceof NoSuchFileException) { - LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException); - return FileVisitResult.CONTINUE; - } else { - return super.visitFileFailed(file, ioException); - } - } - - /** - * Deletes the specified file. - * - * @param file the file to delete - * @throws IOException if a problem occurred deleting the file - */ - protected void delete(final Path file) throws IOException { - LOGGER.trace("Deleting {}", file); - Files.deleteIfExists(file); - } - - /** - * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise. - * - * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise - */ - public boolean isTestMode() { - return testMode; - } -} +/* + * 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.appender.rolling.action; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Objects; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored. + */ +public class DeletingVisitor extends SimpleFileVisitor { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Path basePath; + private final boolean testMode; + private final List pathConditions; + + /** + * Constructs a new DeletingVisitor. + * + * @param basePath used to relativize paths + * @param pathConditions objects that need to confirm whether a file can be deleted + * @param testMode if true, files are not deleted but instead a message is printed to the status logger + * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. + */ + public DeletingVisitor(final Path basePath, final List pathConditions, + final boolean testMode) { + this.testMode = testMode; + this.basePath = Objects.requireNonNull(basePath, "basePath"); + this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions"); + for (final PathCondition condition : pathConditions) { + condition.beforeFileTreeWalk(); + } + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + for (final PathCondition pathFilter : pathConditions) { + final Path relative = basePath.relativize(file); + if (!pathFilter.accept(basePath, relative, attrs)) { + LOGGER.trace("Not deleting base={}, relative={}", basePath, relative); + return FileVisitResult.CONTINUE; + } + } + if (isTestMode()) { + LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file); + } else { + delete(file); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(final Path file, final IOException ioException) throws IOException { + // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from + // failed attempts to load file attributes. + if (ioException instanceof NoSuchFileException) { + LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException); + return FileVisitResult.CONTINUE; + } else { + return super.visitFileFailed(file, ioException); + } + } + + /** + * Deletes the specified file. + * + * @param file the file to delete + * @throws IOException if a problem occurred deleting the file + */ + protected void delete(final Path file) throws IOException { + LOGGER.trace("Deleting {}", file); + Files.deleteIfExists(file); + } + + /** + * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise. + * + * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise + */ + public boolean isTestMode() { + return testMode; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java index 2e765ab0b8c..d8ea3c2d22c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java @@ -1,27 +1,26 @@ -/* - * 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.appender.rolling.action; - -import java.util.Comparator; - -/** - * Defines the interface of classes that can sort Paths. - */ -public interface PathSorter extends Comparator{ - -} +/* + * 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.appender.rolling.action; + +import java.util.Comparator; + +/** + * Defines the interface of classes that can sort Paths. + */ +public interface PathSorter extends Comparator{ + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java index d69835427b9..674e7842694 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java @@ -1,59 +1,58 @@ -/* - * 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.appender.rolling.action; - -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Objects; - -/** - * Tuple of a {@code Path} and {@code BasicFileAttributes}, used for sorting. - */ -public class PathWithAttributes { - - private final Path path; - private final BasicFileAttributes attributes; - - public PathWithAttributes(final Path path, final BasicFileAttributes attributes) { - this.path = Objects.requireNonNull(path, "path"); - this.attributes = Objects.requireNonNull(attributes, "attributes"); - } - - @Override - public String toString() { - return path + " (modified: " + attributes.lastModifiedTime() + ")"; - } - - /** - * Returns the path. - * - * @return the path - */ - public Path getPath() { - return path; - } - - /** - * Returns the attributes. - * - * @return the attributes - */ - public BasicFileAttributes getAttributes() { - return attributes; - } -} +/* + * 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.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; + +/** + * Tuple of a {@code Path} and {@code BasicFileAttributes}, used for sorting. + */ +public class PathWithAttributes { + + private final Path path; + private final BasicFileAttributes attributes; + + public PathWithAttributes(final Path path, final BasicFileAttributes attributes) { + this.path = Objects.requireNonNull(path, "path"); + this.attributes = Objects.requireNonNull(attributes, "attributes"); + } + + @Override + public String toString() { + return path + " (modified: " + attributes.lastModifiedTime() + ")"; + } + + /** + * Returns the path. + * + * @return the path + */ + public Path getPath() { + return path; + } + + /** + * Returns the attributes. + * + * @return the attributes + */ + public BasicFileAttributes getAttributes() { + return attributes; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java index ba7e00b601e..c7ba94ec613 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java @@ -1,75 +1,74 @@ -/* - * 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.appender.rolling.action; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * FileVisitor that sorts files. - */ -public class SortingVisitor extends SimpleFileVisitor { - - private static final Logger LOGGER = StatusLogger.getLogger(); - private final PathSorter sorter; - private final List collected = new ArrayList<>(); - - /** - * Constructs a new DeletingVisitor. - * - * @param basePath used to relativize paths - * @param pathFilters objects that need to confirm whether a file can be deleted - */ - public SortingVisitor(final PathSorter sorter) { - this.sorter = Objects.requireNonNull(sorter, "sorter"); - } - - @Override - public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException { - collected.add(new PathWithAttributes(path, attrs)); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(final Path file, final IOException ioException) throws IOException { - // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from - // failed attempts to load file attributes. - if (ioException instanceof NoSuchFileException) { - LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException); - return FileVisitResult.CONTINUE; - } else { - return super.visitFileFailed(file, ioException); - } - } - - public List getSortedPaths() { - Collections.sort(collected, sorter); - return collected; - } -} +/* + * 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.appender.rolling.action; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * FileVisitor that sorts files. + */ +public class SortingVisitor extends SimpleFileVisitor { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private final PathSorter sorter; + private final List collected = new ArrayList<>(); + + /** + * Constructs a new DeletingVisitor. + * + * @param basePath used to relativize paths + * @param pathFilters objects that need to confirm whether a file can be deleted + */ + public SortingVisitor(final PathSorter sorter) { + this.sorter = Objects.requireNonNull(sorter, "sorter"); + } + + @Override + public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException { + collected.add(new PathWithAttributes(path, attrs)); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(final Path file, final IOException ioException) throws IOException { + // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from + // failed attempts to load file attributes. + if (ioException instanceof NoSuchFileException) { + LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException); + return FileVisitResult.CONTINUE; + } else { + return super.visitFileFailed(file, ioException); + } + } + + public List getSortedPaths() { + Collections.sort(collected, sorter); + return collected; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java index b791f0afe41..1cf0c8e66fb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java @@ -1,66 +1,65 @@ -/* - * 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.async; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.LogEventFactory; -import org.apache.logging.log4j.core.jmx.RingBufferAdmin; - -/** - * Encapsulates the mechanism used to log asynchronously. There is one delegate per configuration, which is shared by - * all AsyncLoggerConfig objects in the configuration. - */ -public interface AsyncLoggerConfigDelegate { - - /** - * Creates and returns a new {@code RingBufferAdmin} that instruments the ringbuffer of this - * {@code AsyncLoggerConfig}. - * - * @param contextName name of the {@code LoggerContext} - * @param loggerConfigName name of the logger config - * @return the RingBufferAdmin that instruments the ringbuffer - */ - RingBufferAdmin createRingBufferAdmin(final String contextName, final String loggerConfigName); - - /** - * Returns the {@code EventRoute} for the event with the specified level. - * - * @param level the level of the event to log - * @return the {@code EventRoute} - */ - EventRoute getEventRoute(final Level level); - - /** - * Enqueues the {@link LogEvent} on the mixed configuration ringbuffer. - * This method must only be used after {@link #tryEnqueue(LogEvent, AsyncLoggerConfig)} returns false - * indicating that the ringbuffer is full, otherwise it may incur unnecessary synchronization. - */ - void enqueueEvent(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); - - boolean tryEnqueue(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); - - /** - * Notifies the delegate what LogEventFactory an AsyncLoggerConfig is using, so the delegate can determine - * whether to populate the ring buffer with mutable log events or not. This method may be invoked multiple times - * for all AsyncLoggerConfigs that use this delegate. - * - * @param logEventFactory the factory used - */ - void setLogEventFactory(LogEventFactory logEventFactory); -} +/* + * 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.async; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.LogEventFactory; +import org.apache.logging.log4j.core.jmx.RingBufferAdmin; + +/** + * Encapsulates the mechanism used to log asynchronously. There is one delegate per configuration, which is shared by + * all AsyncLoggerConfig objects in the configuration. + */ +public interface AsyncLoggerConfigDelegate { + + /** + * Creates and returns a new {@code RingBufferAdmin} that instruments the ringbuffer of this + * {@code AsyncLoggerConfig}. + * + * @param contextName name of the {@code LoggerContext} + * @param loggerConfigName name of the logger config + * @return the RingBufferAdmin that instruments the ringbuffer + */ + RingBufferAdmin createRingBufferAdmin(final String contextName, final String loggerConfigName); + + /** + * Returns the {@code EventRoute} for the event with the specified level. + * + * @param level the level of the event to log + * @return the {@code EventRoute} + */ + EventRoute getEventRoute(final Level level); + + /** + * Enqueues the {@link LogEvent} on the mixed configuration ringbuffer. + * This method must only be used after {@link #tryEnqueue(LogEvent, AsyncLoggerConfig)} returns false + * indicating that the ringbuffer is full, otherwise it may incur unnecessary synchronization. + */ + void enqueueEvent(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); + + boolean tryEnqueue(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); + + /** + * Notifies the delegate what LogEventFactory an AsyncLoggerConfig is using, so the delegate can determine + * whether to populate the ring buffer with mutable log events or not. This method may be invoked multiple times + * for all AsyncLoggerConfigs that use this delegate. + * + * @param logEventFactory the factory used + */ + void setLogEventFactory(LogEventFactory logEventFactory); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java index 9658f0302f9..70f90ca3b68 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java @@ -1,44 +1,44 @@ -/* - * 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.async; - -import java.net.URI; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.selector.BasicContextSelector; -import org.apache.logging.log4j.plugins.Inject; -import org.apache.logging.log4j.plugins.Singleton; -import org.apache.logging.log4j.plugins.di.Injector; - -/** - * Returns either this Thread's context or the default {@link AsyncLoggerContext}. - * Single-application instances should prefer this implementation over the {@link AsyncLoggerContextSelector} - * due to the reduced overhead avoiding classloader lookups. - */ -@Singleton -public class BasicAsyncLoggerContextSelector extends BasicContextSelector { - - @Inject - public BasicAsyncLoggerContextSelector(Injector injector) { - super(injector); - } - - @Override - protected LoggerContext createContext() { - return new AsyncLoggerContext("AsyncDefault", null, (URI) null, injector); - } -} +/* + * 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.async; + +import java.net.URI; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.selector.BasicContextSelector; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; + +/** + * Returns either this Thread's context or the default {@link AsyncLoggerContext}. + * Single-application instances should prefer this implementation over the {@link AsyncLoggerContextSelector} + * due to the reduced overhead avoiding classloader lookups. + */ +@Singleton +public class BasicAsyncLoggerContextSelector extends BasicContextSelector { + + @Inject + public BasicAsyncLoggerContextSelector(Injector injector) { + super(injector); + } + + @Override + protected LoggerContext createContext() { + return new AsyncLoggerContext("AsyncDefault", null, (URI) null, injector); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java index 1ccb74a6b11..b469a73b1d7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java @@ -1,95 +1,94 @@ -/* - * 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.config; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.Supplier; - -/** - * Interface for objects that know how to ensure delivery of log events to the appropriate appenders, even during and - * after the configuration has been modified while the system is actively used. - */ -public interface ReliabilityStrategy { - - /** - * Logs an event. - * - * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active - * @param loggerName The name of the Logger. - * @param fqcn The fully qualified class name of the caller. - * @param marker A Marker or null if none is present. - * @param level The event Level. - * @param data The Message. - * @param t A Throwable or null. - */ - void log(Supplier reconfigured, String loggerName, String fqcn, Marker marker, Level level, - Message data, Throwable t); - /** - * Logs an event. - * - * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active - * @param loggerName The name of the Logger. - * @param fqcn The fully qualified class name of the caller. - * @param location The location of the caller or null. - * @param marker A Marker or null if none is present. - * @param level The event Level. - * @param data The Message. - * @param t A Throwable or null. - * @since 3.0 - */ - default void log(final Supplier reconfigured, final String loggerName, final String fqcn, final StackTraceElement location, - final Marker marker, final Level level, final Message data, final Throwable t) { - } - - /** - * Logs an event. - * - * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active - * @param event The log event. - */ - void log(Supplier reconfigured, LogEvent event); - - /** - * For internal use by the ReliabilityStrategy; returns the LoggerConfig to use. - * - * @param next supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active - * @return the currently active LoggerConfig - */ - LoggerConfig getActiveLoggerConfig(Supplier next); - - /** - * Called after a log event was logged. - */ - void afterLogEvent(); - - /** - * Called before all appenders are stopped. - */ - void beforeStopAppenders(); - - /** - * Called before the configuration is stopped. - * - * @param configuration the configuration that will be stopped - */ - void beforeStopConfiguration(Configuration configuration); - -} +/* + * 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.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Supplier; + +/** + * Interface for objects that know how to ensure delivery of log events to the appropriate appenders, even during and + * after the configuration has been modified while the system is actively used. + */ +public interface ReliabilityStrategy { + + /** + * Logs an event. + * + * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active + * @param loggerName The name of the Logger. + * @param fqcn The fully qualified class name of the caller. + * @param marker A Marker or null if none is present. + * @param level The event Level. + * @param data The Message. + * @param t A Throwable or null. + */ + void log(Supplier reconfigured, String loggerName, String fqcn, Marker marker, Level level, + Message data, Throwable t); + /** + * Logs an event. + * + * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active + * @param loggerName The name of the Logger. + * @param fqcn The fully qualified class name of the caller. + * @param location The location of the caller or null. + * @param marker A Marker or null if none is present. + * @param level The event Level. + * @param data The Message. + * @param t A Throwable or null. + * @since 3.0 + */ + default void log(final Supplier reconfigured, final String loggerName, final String fqcn, final StackTraceElement location, + final Marker marker, final Level level, final Message data, final Throwable t) { + } + + /** + * Logs an event. + * + * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active + * @param event The log event. + */ + void log(Supplier reconfigured, LogEvent event); + + /** + * For internal use by the ReliabilityStrategy; returns the LoggerConfig to use. + * + * @param next supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active + * @return the currently active LoggerConfig + */ + LoggerConfig getActiveLoggerConfig(Supplier next); + + /** + * Called after a log event was logged. + */ + void afterLogEvent(); + + /** + * Called before all appenders are stopped. + */ + void beforeStopAppenders(); + + /** + * Called before the configuration is stopped. + * + * @param configuration the configuration that will be stopped + */ + void beforeStopConfiguration(Configuration configuration); + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java index f6e7e1e6bbe..2ca603dabea 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java @@ -1,47 +1,47 @@ -/* - * 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.config.builder.impl; - -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; - -/** - * Extends {@code DefaultComponentBuilder} to specify - * {@code DefaultConfigurationBuilder} as the - * {@code ConfigurationBuilder} type. - * - * @since 2.4 - */ -class DefaultComponentAndConfigurationBuilder> - extends DefaultComponentBuilder> { - - DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, - final String type, final String value) { - super(builder, name, type, value); - } - - DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, - final String type) { - super(builder, name, type); - } - - public DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, - final String type) { - super(builder, type); - } - -} +/* + * 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.config.builder.impl; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; + +/** + * Extends {@code DefaultComponentBuilder} to specify + * {@code DefaultConfigurationBuilder} as the + * {@code ConfigurationBuilder} type. + * + * @since 2.4 + */ +class DefaultComponentAndConfigurationBuilder> + extends DefaultComponentBuilder> { + + DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, + final String type, final String value) { + super(builder, name, type, value); + } + + DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, + final String type) { + super(builder, name, type); + } + + public DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, + final String type) { + super(builder, type); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java index 2ee6b0d4f6f..1588d2999d4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java @@ -1,31 +1,31 @@ -/* - * 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.selector; - -import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; -import org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector; - -public class CoreContextSelectors { - - public static final Class[] CLASSES = new Class[] { - ClassLoaderContextSelector.class, - BasicContextSelector.class, - AsyncLoggerContextSelector.class, - BasicAsyncLoggerContextSelector.class - }; - -} +/* + * 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.selector; + +import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector; + +public class CoreContextSelectors { + + public static final Class[] CLASSES = new Class[] { + ClassLoaderContextSelector.class, + BasicContextSelector.class, + AsyncLoggerContextSelector.class, + BasicAsyncLoggerContextSelector.class + }; + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java index 1139bd20f99..946e0c29b4d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java @@ -1,60 +1,60 @@ -/* - * 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.util; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * A delegating OutputStream that does not close its delegate. - */ -public class CloseShieldOutputStream extends OutputStream { - - private final OutputStream delegate; - - public CloseShieldOutputStream(final OutputStream delegate) { - this.delegate = delegate; - } - - /** - * Does nothing. - */ - @Override - public void close() { - // do not close delegate - } - - @Override - public void flush() throws IOException { - delegate.flush(); - } - - @Override - public void write(final byte[] b) throws IOException { - delegate.write(b); - } - - @Override - public void write(final byte[] b, final int off, final int len) throws IOException { - delegate.write(b, off, len); - } - - @Override - public void write(final int b) throws IOException { - delegate.write(b); - } +/* + * 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.util; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A delegating OutputStream that does not close its delegate. + */ +public class CloseShieldOutputStream extends OutputStream { + + private final OutputStream delegate; + + public CloseShieldOutputStream(final OutputStream delegate) { + this.delegate = delegate; + } + + /** + * Does nothing. + */ + @Override + public void close() { + // do not close delegate + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void write(final byte[] b) throws IOException { + delegate.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + delegate.write(b, off, len); + } + + @Override + public void write(final int b) throws IOException { + delegate.write(b); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldWriter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldWriter.java index c29cdf624ec..b2f6fdcd9dc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldWriter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldWriter.java @@ -1,46 +1,46 @@ -/* - * 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.util; - -import java.io.IOException; -import java.io.Writer; - -public class CloseShieldWriter extends Writer { - - private final Writer delegate; - - public CloseShieldWriter(final Writer delegate) { - this.delegate = delegate; - } - - @Override - public void close() throws IOException { - // do not close delegate - } - - @Override - public void flush() throws IOException { - delegate.flush(); - - } - - @Override - public void write(final char[] cbuf, final int off, final int len) throws IOException { - delegate.write(cbuf, off, len); - } - -} +/* + * 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.util; + +import java.io.IOException; +import java.io.Writer; + +public class CloseShieldWriter extends Writer { + + private final Writer delegate; + + public CloseShieldWriter(final Writer delegate) { + this.delegate = delegate; + } + + @Override + public void close() throws IOException { + // do not close delegate + } + + @Override + public void flush() throws IOException { + delegate.flush(); + + } + + @Override + public void write(final char[] cbuf, final int off, final int len) throws IOException { + delegate.write(cbuf, off, len); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java index ffb8615cd3c..07e5c793163 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java @@ -1,130 +1,130 @@ -/* - * 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.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Writer; - -/** - * Copied from Apache Commons IO revision 1686747. - */ -public class IOUtils { - - /** - * The default buffer size ({@value}) to use for - * {@link #copyLarge(InputStream, OutputStream)} - * and - * {@link #copyLarge(Reader, Writer)} - */ - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - /** - * Represents the end-of-file (or stream). - */ - public static final int EOF = -1; - - /** - * Copies chars from a Reader to a Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Large streams (over 2GB) will return a chars copied value of - * -1 after the copy has completed since the correct - * number of chars cannot be returned as an int. For large streams - * use the copyLarge(Reader, Writer) method. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied, or -1 if > Integer.MAX_VALUE - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since 1.1 - */ - public static int copy(final Reader input, final Writer output) throws IOException { - final long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copies chars from a large (over 2GB) Reader to a Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since 1.3 - */ - public static long copyLarge(final Reader input, final Writer output) throws IOException { - return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copies chars from a large (over 2GB) Reader to a Writer. - *

- * This method uses the provided buffer, so there is no need to use a - * BufferedReader. - *

- * - * @param input the Reader to read from - * @param output the Writer to write to - * @param buffer the buffer to be used for the copy - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { - long count = 0; - int n; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Gets the contents of a Reader as a String. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static String toString(final Reader input) throws IOException { - final StringBuilderWriter sw = new StringBuilderWriter(); - copy(input, sw); - return sw.toString(); - } - -} +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; + +/** + * Copied from Apache Commons IO revision 1686747. + */ +public class IOUtils { + + /** + * The default buffer size ({@value}) to use for + * {@link #copyLarge(InputStream, OutputStream)} + * and + * {@link #copyLarge(Reader, Writer)} + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Represents the end-of-file (or stream). + */ + public static final int EOF = -1; + + /** + * Copies chars from a Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Large streams (over 2GB) will return a chars copied value of + * -1 after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the copyLarge(Reader, Writer) method. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(final Reader input, final Writer output) throws IOException { + final long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copies chars from a large (over 2GB) Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(final Reader input, final Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copies chars from a large (over 2GB) Reader to a Writer. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param buffer the buffer to be used for the copy + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { + long count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Gets the contents of a Reader as a String. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(final Reader input) throws IOException { + final StringBuilderWriter sw = new StringBuilderWriter(); + copy(input, sw); + return sw.toString(); + } + +} diff --git a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java index c28021a82f7..98047a0cff4 100644 --- a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java +++ b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java @@ -172,6 +172,11 @@ void resetSendRcs() { sendRcTrue = sendRcFalse = 0; } + // not public, handy for testing + byte[] recv(int timeoutMs) { + return manager.recv(timeoutMs); + } + @Override public String toString() { return "JeroMqAppender{" + diff --git a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqManager.java b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqManager.java index e50972df03e..51aaf1b8404 100644 --- a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqManager.java +++ b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqManager.java @@ -26,6 +26,7 @@ import org.apache.logging.log4j.core.util.Cancellable; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; import org.apache.logging.log4j.util.PropertiesUtil; +import org.zeromq.SocketType; import org.zeromq.ZMQ; /** @@ -60,7 +61,7 @@ public class JeroMqManager extends AbstractManager { final boolean enableShutdownHook = PropertiesUtil.getProperties().getBooleanProperty( SYS_PROPERTY_ENABLE_SHUTDOWN_HOOK, true); - if (enableShutdownHook) { + if (enableShutdownHook && LogManager.getFactory() instanceof ShutdownCallbackRegistry) { SHUTDOWN_HOOK = ((ShutdownCallbackRegistry) LogManager.getFactory()).addShutdownCallback(CONTEXT::close); } else { SHUTDOWN_HOOK = null; @@ -71,7 +72,7 @@ public class JeroMqManager extends AbstractManager { private JeroMqManager(final String name, final JeroMqConfiguration config) { super(null, name); - publisher = CONTEXT.socket(ZMQ.PUB); + publisher = CONTEXT.socket(SocketType.XPUB); publisher.setAffinity(config.affinity); publisher.setBacklog(config.backlog); publisher.setDelayAttachOnConnect(config.delayAttachOnConnect); @@ -104,6 +105,17 @@ public boolean send(final byte[] data) { return publisher.send(data); } + // not public, handy for testing + byte[] recv(int timeoutMs) { + int oldTimeoutMs = publisher.getReceiveTimeOut(); + try { + publisher.setReceiveTimeOut(timeoutMs); + return publisher.recv(); + } finally { + publisher.setReceiveTimeOut(oldTimeoutMs); + } + } + @Override protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { publisher.close(); diff --git a/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppenderTest.java b/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppenderTest.java index e5825592817..aafe354a5d8 100644 --- a/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppenderTest.java +++ b/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppenderTest.java @@ -21,6 +21,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Logger; @@ -31,13 +32,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; @Tag("zeromq") -@Tag("sleepy") -@Timeout(value = 200) +@Timeout(value = 10, unit = TimeUnit.SECONDS) @LoggerContextSource(value = "JeroMqAppenderTest.xml", timeout = 60) public class JeroMqAppenderTest { @@ -61,7 +61,7 @@ public void testClientServer(@Named(APPENDER_NAME) final JeroMqAppender appender final ExecutorService executor = Executors.newSingleThreadExecutor(); try { final Future> future = executor.submit(client); - Thread.sleep(100); + waitForSubscription(appender, 1000); appender.resetSendRcs(); logger.info("Hello"); logger.info("Again"); @@ -87,7 +87,7 @@ public void testMultiThreadedServer(@Named(APPENDER_NAME) final JeroMqAppender a final ExecutorService executor = Executors.newSingleThreadExecutor(); try { final Future> future = executor.submit(client); - Thread.sleep(100); + waitForSubscription(appender, 1000); appender.resetSendRcs(); final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads); for (int i = 0; i < 10.; i++) { @@ -129,4 +129,15 @@ public void testServerOnly(@Named(APPENDER_NAME) final JeroMqAppender appender, assertEquals(2, appender.getSendRcTrue()); assertEquals(0, appender.getSendRcFalse()); } + + private void waitForSubscription(JeroMqAppender appender, int timeoutMs) throws Exception { + final long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < timeoutMs) { + byte[] msg = appender.recv(timeoutMs); + if (msg != null && msg.length > 0 && msg[0] == 1) { + return; + } + } + throw new TimeoutException(); + } } diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java index 07f3234060b..6a8a4a90e94 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java @@ -75,14 +75,17 @@ public static void usingSerializedLogEventAccessor( final Consumer accessorConsumer) { final String serializedLogEventJson = layout.toSerializable(logEvent); @SuppressWarnings("unchecked") - final Map deserializedLogEvent = - (Map) readJson(serializedLogEventJson); + final Map deserializedLogEvent = (Map) readJson(serializedLogEventJson); final MapAccessor serializedLogEventAccessor = new MapAccessor(deserializedLogEvent); accessorConsumer.accept(serializedLogEventAccessor); } public static Object readJson(final String json) { - return JsonReader.read(json); + try { + return JsonReader.read(json); + } catch (final Exception error) { + throw new RuntimeException("failed to deserialize the JSON: " + json, error); + } } public static Map asMap(final Object... pairs) { diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverTest.java new file mode 100644 index 00000000000..8eeb6f7f4a1 --- /dev/null +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverTest.java @@ -0,0 +1,94 @@ +/* + * 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.resolver; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout; +import org.junit.jupiter.api.Test; + +import static org.apache.logging.log4j.layout.template.json.TestHelpers.*; +import static org.assertj.core.api.Assertions.assertThat; + +class MarkerResolverTest { + + @Test + void should_have_a_marker_name() { + + // Create the event template + final String eventTemplate = writeJson(asMap( + "marker", asMap( + "$resolver", "marker", + "field", "name"))); + + // Create the layout. + final JsonTemplateLayout layout = JsonTemplateLayout + .newBuilder() + .setConfiguration(CONFIGURATION) + .setEventTemplate(eventTemplate) + .build(); + + // Create the log event. + final Marker marker = MarkerManager.getMarker("MARKER"); + final LogEvent logEvent = Log4jLogEvent + .newBuilder() + .setMarker(marker) + .build(); + + // Check the serialized event. + usingSerializedLogEventAccessor(layout, logEvent, accessor -> + assertThat(accessor.getString("marker")).isEqualTo("MARKER")); + + } + + @Test + void should_list_parents_as_array() { + + // Create the event template + final String eventTemplate = writeJson(asMap( + "parents", asMap( + "$resolver", "marker", + "field", "parents"))); + + // Create the layout. + final JsonTemplateLayout layout = JsonTemplateLayout + .newBuilder() + .setConfiguration(CONFIGURATION) + .setEventTemplate(eventTemplate) + .build(); + + // Create the log event. + final Marker parentMarker1 = MarkerManager.getMarker("PARENT_MARKER_NAME_1"); + final Marker parentMarker2 = MarkerManager.getMarker("PARENT_MARKER_NAME_2"); + final Marker childMarker = MarkerManager.getMarker("CHILD_MARKER_NAME"); + childMarker.setParents(parentMarker1, parentMarker2); + + final LogEvent logEvent = Log4jLogEvent + .newBuilder() + .setMarker(childMarker) + .build(); + + // Check the serialized event. + usingSerializedLogEventAccessor(layout, logEvent, accessor -> + assertThat(accessor.getList("parents", String.class)) + .containsOnly(parentMarker1.getName(), parentMarker2.getName())); + + } + +} diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java index fc1c30c9bcf..4975fd2a15b 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java @@ -26,7 +26,7 @@ *

Configuration

* *
- * config = "field" -> "name"
+ * config = "field" -> ( "name" | "parents" )
  * 
* *

Examples

@@ -39,6 +39,15 @@ * "field": "name" * } * + * + * Resolve the names of the marker's parents: + * + *
+ * {
+ *   "$resolver": "marker",
+ *   "field": "parents"
+ * }
+ * 
*/ public final class MarkerResolver implements EventResolver { @@ -52,6 +61,30 @@ public final class MarkerResolver implements EventResolver { } }; + private static final TemplateResolver PARENTS_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + + // Short-circuit if there are no parents + final Marker marker = logEvent.getMarker(); + if (marker == null || !marker.hasParents()) { + jsonWriter.writeNull(); + return; + } + + // Write parents + final Marker[] parents = marker.getParents(); + jsonWriter.writeArrayStart(); + for (int parentIndex = 0; parentIndex < parents.length; parentIndex++) { + if (parentIndex > 0) { + jsonWriter.writeSeparator(); + } + final Marker parentMarker = parents[parentIndex]; + jsonWriter.writeString(parentMarker.getName()); + } + jsonWriter.writeArrayEnd(); + + }; + private final TemplateResolver internalResolver; MarkerResolver(final TemplateResolverConfig config) { @@ -61,9 +94,15 @@ public final class MarkerResolver implements EventResolver { private TemplateResolver createInternalResolver( final TemplateResolverConfig config) { final String fieldName = config.getString("field"); + if ("name".equals(fieldName)) { return NAME_RESOLVER; } + + if ("parents".equals(fieldName)) { + return PARENTS_RESOLVER; + } + throw new IllegalArgumentException("unknown field: " + config); } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java index dbeb78232d9..18d0093b886 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java @@ -16,12 +16,7 @@ */ package org.apache.logging.log4j.plugins.internal.util; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; class HierarchicalCollections { diff --git a/log4j-to-slf4j/pom.xml b/log4j-to-slf4j/pom.xml index 66fe3e4a8d4..f7543705b06 100644 --- a/log4j-to-slf4j/pom.xml +++ b/log4j-to-slf4j/pom.xml @@ -31,6 +31,7 @@ SLF4J Documentation /log4j-to-slf4j org.apache.logging.slf4j + 3 @@ -91,6 +92,7 @@ org.apache.logging.slf4j + org.slf4j*;version="${range;[==,${slf4j.support.bound})}",* diff --git a/pom.xml b/pom.xml index 786d2c52aa9..9f6cb8412f6 100644 --- a/pom.xml +++ b/pom.xml @@ -434,6 +434,7 @@ 9.4.50.v20221201 3.5.8 1.36 + 5.12.1 2.36.0 4.13.2 5.9.1 @@ -982,6 +983,12 @@ ${jmh.version} + + net.java.dev.jna + jna + ${jna.version} + + net.javacrumbs.json-unit json-unit @@ -1353,7 +1360,7 @@ - org.apache.felix + org.apache.maven.plugins maven-artifact-plugin ${maven-artifact-plugin.version} diff --git a/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml b/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml new file mode 100644 index 00000000000..b166412c830 --- /dev/null +++ b/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml @@ -0,0 +1,29 @@ + + + + + + + + Adapt the OSGi metadata of log4j-to-slf4j to work with slf4j 1 and 2. + To achieve that use a version range of `[1.7,3)` for the imported slf4j packages. + + diff --git a/src/changelog/.2.x.x/1366_fix_java_sql_date.xml b/src/changelog/.2.x.x/1366_fix_java_sql_date.xml new file mode 100644 index 00000000000..8a28a5bd2e4 --- /dev/null +++ b/src/changelog/.2.x.x/1366_fix_java_sql_date.xml @@ -0,0 +1,28 @@ + + + + + + + + Fixed logging of java.sql.Date objects by appending it before Log4J tries to call java.util.Date.toInstant() on it. + + diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm index f1bb103a21e..ad61105033a 100644 --- a/src/site/asciidoc/manual/json-template-layout.adoc.vm +++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm @@ -743,6 +743,8 @@ Each `pointMatcherRegexes` item triggers a `Pattern#matcher()` call, which is not garbage-free either. ==== +====== Examples + Resolve `logEvent.getThrown().getClass().getCanonicalName()`: [source,json] @@ -849,6 +851,8 @@ severity-field = "field" -> ( "keyword" | "code" ) Resolves the fields of the `logEvent.getLevel()`. +====== Examples + Resolve the level name: [source,json] @@ -897,6 +901,8 @@ config = "field" -> ( "name" | "fqcn" ) Resolves `logEvent.getLoggerFqcn()` and `logEvent.getLoggerName()`. +====== Examples + Resolve the logger name: [source,json] @@ -930,6 +936,8 @@ key = "key" -> string Performs link:lookups.html#AppMainArgsLookup[Main Argument Lookup] for the given `index` or `key`. +====== Examples + Resolve the 1st `main()` method argument: [source,json] @@ -956,6 +964,38 @@ Resolve the argument coming right after `--userId`: Resolves ``MapMessage``s. See link:#map-resolver-template[Map Resolver Template] for details. +[#event-template-resolver-marker] +===== `marker` + +[source] +---- +config = "field" -> ( "name" | "parents" ) +---- + +Resolves `logEvent.getMarker()`. + +====== Examples + +Resolve the marker name: + +[source,json] +---- +{ + "$resolver": "marker", + "field": "name" +} +---- + +Resolve the names of the marker's parents: + +[source,json] +---- +{ + "$resolver": "marker", + "field": "parents" +} +---- + [#event-template-resolver-mdc] ===== `mdc` @@ -986,6 +1026,8 @@ For simple string messages, the resolution is performed without allocations. For ``ObjectMessage``s and ``MultiformatMessage``s, it depends. ==== +====== Examples + Resolve the message into a string: [source,json] @@ -1047,6 +1089,8 @@ Regarding garbage footprint, `stringified` flag translates to which is the case if `log4j2.enableThreadLocals` property set to true. ==== +====== Examples + Resolve the message parameters into an array: [source,json] @@ -1099,6 +1143,8 @@ pattern = "pattern" -> string Resolves the Nested Diagnostic Context (NDC), aka. Thread Context Stack, `String[]` returned by `logEvent.getContextStack()`. +====== Examples + Resolve all NDC values into a list: [source,json] @@ -1133,6 +1179,8 @@ Resolver delegating to link:layouts.html#PatternLayout[`PatternLayout`]. The default value of `stackTraceEnabled` is inherited from the parent `JsonTemplateLayout`. +====== Examples + Resolve the string produced by `%p %c{1.} [%t] %X{userId} %X %m%ex` pattern: [source,json] @@ -1161,6 +1209,8 @@ Resolves the fields of the `StackTraceElement` returned by Note that this resolver is toggled by `log4j.layout.jsonTemplate.locationInfoEnabled` property. +====== Examples + Resolve the line number: [source,json] @@ -1182,6 +1232,8 @@ config = "field" -> ( "name" | "id" | "priority" ) Resolves `logEvent.getThreadId()`, `logEvent.getThreadName()`, `logEvent.getThreadPriority()`. +====== Examples + Resolve the thread name: [source,json] @@ -1221,6 +1273,8 @@ rounded = "rounded" -> boolean Resolves `logEvent.getInstant()` in various forms. +====== Examples + .`timestamp` template resolver examples [cols="5,2m"] |=== From 7cdd07593178281d69afda32746a3452016f5a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Fri, 24 Mar 2023 22:24:46 +0100 Subject: [PATCH 24/39] More structural fixes --- .../log4j/spi/RecyclerFactoriesTest.java | 101 +++++++++++++ .../spi/ThreadLocalRecyclerFactoryTest.java | 36 +++-- .../log4j/util/StringParameterParserTest.java | 49 +++--- .../logging/log4j/message/MessageFactory.java | 4 +- .../message/ReusableParameterizedMessage.java | 5 +- .../logging/log4j/spi/AbstractLogger.java | 5 +- .../log4j/spi/QueueingRecyclerFactory.java | 14 +- .../logging/log4j/spi/RecyclerFactories.java | 64 +++----- .../log4j/spi/ThreadLocalRecyclerFactory.java | 16 +- .../logging/log4j/status/StatusLogger.java | 4 +- .../util/{Queues.java => QueueFactories.java} | 106 ++++++++----- .../logging/log4j/util/QueueFactory.java | 9 ++ .../log4j/util/StringParameterParser.java | 15 +- .../log4j/core/impl/MutableLogEvent.java | 3 +- .../log4j/spi/RecyclerFactoriesTest.java | 140 ------------------ .../org/apache/logging/slf4j/SLF4JLogger.java | 11 +- 16 files changed, 290 insertions(+), 292 deletions(-) create mode 100644 log4j-api-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java rename {log4j-layout-template-json-test => log4j-api-test}/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java (88%) rename log4j-api/src/main/java/org/apache/logging/log4j/util/{Queues.java => QueueFactories.java} (66%) delete mode 100644 log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java 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..265b5cb16fa --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java @@ -0,0 +1,101 @@ +/* + * 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.junit.jupiter.api.Test; + +public class RecyclerFactoriesTest { + + @Test + void DummyRecyclerFactory_should_work() { + final Object actualDummyRecyclerFactory = RecyclerFactories.ofSpec("dummy"); + Assertions + .assertThat(actualDummyRecyclerFactory) + .isSameAs(DummyRecyclerFactory.getInstance()); + } + + @Test + void ThreadLocalRecyclerFactory_should_work() { + final Object actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal"); + Assertions + .assertThat(actualThreadLocalRecyclerFactory) + .isSameAs(ThreadLocalRecyclerFactory.getInstance()); + } + + @Test + void QueueingRecyclerFactory_should_work() { + final Object actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue"); + Assertions + .assertThat(actualQueueingRecyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + } + + @Test + void QueueingRecyclerFactory_should_work_with_supplier() { + final Object 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 Object actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue:capacity=100"); + Assertions + .assertThat(actualQueueingRecyclerFactory) + .isInstanceOf(QueueingRecyclerFactory.class); + } + + @Test + void QueueingRecyclerFactory_should_work_with_supplier_and_capacity() { + final Object 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 index b7ab3393759..71334fdb909 100644 --- 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 @@ -30,44 +30,38 @@ class ThreadLocalRecyclerFactoryTest { - static class RecyclableObject { - boolean using; - boolean returned; - } + private static class RecyclableObject {} private Recycler recycler; - private Queue getRecyclerQueue() { - return ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue(); - } + private Queue recyclerQueue; @BeforeEach void setUp() { - recycler = ThreadLocalRecyclerFactory.getInstance().create(RecyclableObject::new, object -> { - object.using = true; - object.returned = false; - }); + recycler = ThreadLocalRecyclerFactory.getInstance().create(RecyclableObject::new); + recyclerQueue = ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue(); } @ParameterizedTest @IntRangeSource(from = 1, to = ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE, closed = true) - void nestedAcquiresDoNotInterfere(int acquisitionCount) { + void nested_acquires_should_not_interfere(final int acquisitionCount) { + // pool should start empty - assertThat(getRecyclerQueue()).isEmpty(); + assertThat(recyclerQueue).isEmpty(); final List acquiredObjects = IntStream.range(0, acquisitionCount) .mapToObj(i -> recycler.acquire()) .collect(Collectors.toList()); // still nothing returned to pool - assertThat(getRecyclerQueue()).isEmpty(); + 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(getRecyclerQueue()).hasSize(acquisitionCount); + 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 @@ -76,11 +70,13 @@ void nestedAcquiresDoNotInterfere(int acquisitionCount) { .collect(Collectors.toList()); assertThat(reacquiredObjects).containsExactlyElementsOf(acquiredObjects); + } @Test - void nestedAcquiresPastMaximumQueueSizeShouldDiscardExtraReleases() { - assertThat(getRecyclerQueue()).isEmpty(); + 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) @@ -88,13 +84,15 @@ void nestedAcquiresPastMaximumQueueSizeShouldDiscardExtraReleases() { .collect(Collectors.toList()); // still nothing returned to pool - assertThat(getRecyclerQueue()).isEmpty(); + assertThat(recyclerQueue).isEmpty(); // don't want any duplicate instances assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects); acquiredObjects.forEach(recycler::release); // upon return, we should only have ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE retained for future use - assertThat(getRecyclerQueue()).hasSize(ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE); + assertThat(recyclerQueue).hasSize(ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE); + } + } diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/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/util/StringParameterParserTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java index 9e17e08e126..e37dc0a1598 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringParameterParserTest.java @@ -16,18 +16,9 @@ */ package org.apache.logging.log4j.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.util.StringParameterParser; -import org.apache.logging.log4j.util.StringParameterParser.DoubleQuotedStringValue; -import org.apache.logging.log4j.util.StringParameterParser.NullValue; -import org.apache.logging.log4j.util.StringParameterParser.StringValue; -import org.apache.logging.log4j.util.StringParameterParser.Value; -import org.apache.logging.log4j.util.StringParameterParser.Values; +import java.util.*; + +import org.apache.logging.log4j.util.StringParameterParser.*; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -94,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()); @@ -105,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()); @@ -116,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()); @@ -128,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")); }}); @@ -145,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")); }}); @@ -155,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")); }}); @@ -165,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")); }}); @@ -189,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")); }}); @@ -209,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")); }}); @@ -219,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")); }}); @@ -229,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")); }}); @@ -239,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")); }}); @@ -259,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\"")); }}); @@ -269,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")); }}); @@ -279,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")); }}); @@ -289,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,")); }}); @@ -299,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/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java index 142d63b1d36..fa777d31104 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,6 +16,8 @@ */ 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 @@ -243,7 +245,7 @@ default Message newMessage(final String message, final Object p0, final Object p * Recycles a message back for potential reuse or cleanup. * * @since 3.0.0 - * @see org.apache.logging.log4j.spi.Recycler + * @see Recycler */ default void recycle(Message message) { if (message instanceof ReusableMessage) { 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 24982934d27..4318a81b485 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 @@ -46,7 +46,7 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV private Object[] params = new Object[MAX_PARMS]; private Throwable throwable; - private final Recycler bufferRecycler; // non-static: LOG4J2-1583 + private final Recycler bufferRecycler; /** * Creates a reusable message. @@ -59,7 +59,8 @@ public ReusableParameterizedMessage(final RecyclerFactory recyclerFactory) { bufferRecycler = recyclerFactory.create( () -> { final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length(); - return new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2)); + int capacity = Math.max(MIN_BUILDER_SIZE, Math.multiplyExact(currentPatternLength, 2)); + return new StringBuilder(capacity); }, buffer -> { StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); 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 043da09bc6d..06277b481d6 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 @@ -69,7 +69,8 @@ public abstract class AbstractLogger implements ExtendedLogger { private final MessageFactory messageFactory; private final FlowMessageFactory flowMessageFactory; private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031 - protected final Recycler recycler = LoggingSystem.getRecyclerFactory() + protected final Recycler recycler = LoggingSystem + .getRecyclerFactory() .create(() -> new DefaultLogBuilder(this, null)); /** @@ -2747,7 +2748,7 @@ public LogBuilder atLevel(final Level level) { * @since 2.20.0 */ protected LogBuilder getLogBuilder(Level level) { - DefaultLogBuilder builder = (DefaultLogBuilder) recycler.acquire(); + DefaultLogBuilder builder = recycler.acquire(); return builder.reset(this, level); } 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 index 7579566ef1f..e0b5a17a425 100644 --- 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 @@ -22,21 +22,28 @@ 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 = 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. + // Visible for tests static class QueueingRecycler implements Recycler { private final Supplier supplier; @@ -54,7 +61,7 @@ private QueueingRecycler( this.queue = queue; } - // Visible for tests. + // Visible for tests Queue getQueue() { return queue; } @@ -67,6 +74,7 @@ public V acquire() { @Override public void release(final V value) { + requireNonNull(value, "value"); cleaner.accept(value); queue.offer(value); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactories.java index 665bd2b215a..55510019c88 100644 --- 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 @@ -19,26 +19,24 @@ 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.Queues; import org.apache.logging.log4j.util.StringParameterParser; import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; public final class RecyclerFactories { - private RecyclerFactories() {} + private static final int DEFAULT_QUEUE_CAPACITY = Math.max( + 2 * Runtime.getRuntime().availableProcessors() + 1, + 8); - private static int getDefaultCapacity() { - return Math.max( - 2 * Runtime.getRuntime().availableProcessors() + 1, - 8); - } + private RecyclerFactories() {} public static RecyclerFactory getDefault() { return isThreadLocalsEnabled() ? ThreadLocalRecyclerFactory.getInstance() - : new QueueingRecyclerFactory(Queues.MPMC.factory(getDefaultCapacity())); + : new QueueingRecyclerFactory(QueueFactories.MPMC.factory(DEFAULT_QUEUE_CAPACITY)); } public static RecyclerFactory ofSpec(final String recyclerFactorySpec) { @@ -60,11 +58,7 @@ else if (recyclerFactorySpec.equals("threadLocal")) { // Is a queueing factory requested? else if (recyclerFactorySpec.startsWith("queue")) { - - // Determine the default capacity. - final int defaultCapacity = getDefaultCapacity(); - - return readQueueingRecyclerFactory(recyclerFactorySpec, defaultCapacity); + return readQueueingRecyclerFactory(recyclerFactorySpec); } // Bogus input, bail out. @@ -75,53 +69,41 @@ else if (recyclerFactorySpec.startsWith("queue")) { } - private static RecyclerFactory readQueueingRecyclerFactory( - final String recyclerFactorySpec, - final int defaultCapacity) { + private static RecyclerFactory readQueueingRecyclerFactory(final String recyclerFactorySpec) { // Parse the spec. final String queueFactorySpec = recyclerFactorySpec.substring( - "queue".length() + - (recyclerFactorySpec.startsWith("queue:") - ? 1 - : 0)); + "queue".length() + (recyclerFactorySpec.startsWith("queue:") ? 1 : 0)); final Map parsedValues = - StringParameterParser.parse( - queueFactorySpec, Set.of("supplier", "capacity")); + StringParameterParser.parse(queueFactorySpec, Set.of("supplier", "capacity")); // Read the capacity. final StringParameterParser.Value capacityValue = parsedValues.get("capacity"); final int capacity; if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) { - capacity = defaultCapacity; + capacity = DEFAULT_QUEUE_CAPACITY; } else { try { capacity = Integer.parseInt(capacityValue.toString()); } catch (final NumberFormatException error) { throw new IllegalArgumentException( - "failed reading capacity in queueing recycler " + - "factory: " + queueFactorySpec, error); + "failed reading capacity in queueing recycler factory: " + queueFactorySpec, + error); } } - // Read the supplier path. + // Read the supplier path final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); - final String supplierPath; - if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) { - supplierPath = null; - } else { - supplierPath = supplierValue.toString(); - } - - // Execute the read spec. - final QueueFactory queueFactory; - if (supplierPath != null) { - queueFactory = Queues.createQueueFactory(supplierPath, capacity); - } else { - queueFactory = Queues.MPMC.factory(capacity); - } - + 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); + } } 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 index 65a8d1915f7..43144a5ae8c 100644 --- 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 @@ -20,13 +20,14 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import org.apache.logging.log4j.util.Queues; +import org.apache.logging.log4j.util.QueueFactories; /** - * Recycling strategy that caches instances in a ThreadLocal value to allow threads to reuse objects. This strategy - * may not be appropriate in workloads where units of work are independent of operating system threads such as - * reactive streams, coroutines, or virtual threads; a {@linkplain QueueingRecyclerFactory queue-based approach} - * is more flexible. + * 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 */ @@ -39,8 +40,7 @@ public class ThreadLocalRecyclerFactory implements RecyclerFactory { // Visible for testing static final int MAX_QUEUE_SIZE = 8; - private static final ThreadLocalRecyclerFactory INSTANCE = - new ThreadLocalRecyclerFactory(); + private static final ThreadLocalRecyclerFactory INSTANCE = new ThreadLocalRecyclerFactory(); private ThreadLocalRecyclerFactory() {} @@ -65,7 +65,7 @@ static class ThreadLocalRecycler implements Recycler { private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner) { this.supplier = supplier; this.cleaner = cleaner; - this.holder = ThreadLocal.withInitial(() -> Queues.SPSC.create(MAX_QUEUE_SIZE)); + this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(MAX_QUEUE_SIZE)); } @Override 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 d25e3aedf62..6387829e2fb 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 @@ -34,7 +34,7 @@ import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.spi.LoggingSystemProperties; import org.apache.logging.log4j.util.LowLevelLogUtil; -import org.apache.logging.log4j.util.Queues; +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}. @@ -97,7 +97,7 @@ public final class StatusLogger extends AbstractLogger { this.logger = logger; this.configuration = configuration; this.listenersLevel = configuration.getDefaultLevel().intLevel(); - messages = Queues.MPMC.create(configuration.getMaxEntries()); + messages = QueueFactories.MPMC.create(configuration.getMaxEntries()); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java similarity index 66% rename from log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java index 2f0167f2ea9..0d2f04216b2 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Queues.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactories.java @@ -27,38 +27,40 @@ import org.jctools.queues.SpscArrayQueue; /** - * Provides {@link QueueFactory} and {@link Queue} instances for different use cases. When the - * JCTools library is included at runtime, then - * the specialized lock free or wait free queues are used from there. Otherwise, {@link ArrayBlockingQueue} - * is provided as a fallback for thread-safety. Custom implementations of {@link QueueFactory} may also be - * created via {@link #createQueueFactory(String, int)}. + * 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 Queues { +public enum QueueFactories { + /** - * Provides a bounded queue for single-producer/single-consumer usage. Only one thread may offer objects - * while only one thread may poll for them. + * 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. Any thread may offer objects while only - * one thread may poll for them. + * 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. Only one thread may offer objects but - * any thread may poll for them. + * 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. Any thread may offer objects and any thread - * may poll for them. + * Provides a bounded queue for multi-producer/multi-consumer usage. */ MPMC(Lazy.lazy(JCToolsQueueFactory.MPMC::load)); private final Lazy queueFactory; - Queues(final Lazy queueFactory) { + QueueFactories(final Lazy queueFactory) { this.queueFactory = queueFactory; } @@ -70,10 +72,25 @@ 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) { - throw new IllegalArgumentException("invalid supplier in queue factory: " + supplierPath); + 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); @@ -81,22 +98,24 @@ public static QueueFactory createQueueFactory(final String supplierPath, final i final Class supplierClass = LoaderUtil.loadClass(supplierClassName); final BoundedQueueFactory queueFactory; if ("new".equals(supplierMethodName)) { - final Constructor supplierCtor = - supplierClass.getDeclaredConstructor(int.class); + final Constructor supplierCtor = supplierClass.getDeclaredConstructor(int.class); queueFactory = new ConstructorProvidedQueueFactory(supplierCtor); } else { - final Method supplierMethod = - supplierClass.getMethod(supplierMethodName, int.class); + final Method supplierMethod = supplierClass.getMethod(supplierMethodName, int.class); queueFactory = new StaticMethodProvidedQueueFactory(supplierMethod); } return new ProxyQueueFactory(queueFactory, capacity); } catch (final ReflectiveOperationException | LinkageError | SecurityException error) { - throw new RuntimeException("failed executing queue factory", error); + final String message = String.format( + "failed to create the queue factory using the supplier path `%s`", supplierPath); + throw new RuntimeException(message, error); } } - private static class ProxyQueueFactory implements QueueFactory { + private static final class ProxyQueueFactory implements QueueFactory { + private final BoundedQueueFactory factory; + private final int capacity; private ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) { @@ -108,38 +127,52 @@ private ProxyQueueFactory(final BoundedQueueFactory factory, final int capacity) public Queue create() { return factory.create(capacity); } + } + @FunctionalInterface private interface BoundedQueueFactory { + Queue create(final int capacity); + } - private static class ArrayBlockingQueueFactory implements BoundedQueueFactory { + 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) { @@ -147,21 +180,20 @@ public Queue create(final int capacity) { } }; - BoundedQueueFactory load() { + private BoundedQueueFactory load() { try { - // if JCTools is unavailable at runtime, then we'll only find out once we attempt to invoke - // BoundedQueueFactory::create which is the first time the ClassLoader will try to link the - // referenced JCTools class causing a NoClassDefFoundError or some other LinkageError potentially. - // also, test with a large enough capacity to avoid any IllegalArgumentExceptions from trivial queues + // Test with a large enough capacity to avoid any `IllegalArgumentExceptions` from trivial queues create(16); return this; } catch (final LinkageError ignored) { - return new ArrayBlockingQueueFactory(); + return ArrayBlockingQueueFactory.INSTANCE; } } + } - private static class ConstructorProvidedQueueFactory implements BoundedQueueFactory { + private static final class ConstructorProvidedQueueFactory implements BoundedQueueFactory { + private final Constructor constructor; private ConstructorProvidedQueueFactory(final Constructor constructor) { @@ -173,13 +205,15 @@ public Queue create(final int capacity) { final Constructor> typedConstructor = Cast.cast(constructor); try { return typedConstructor.newInstance(capacity); - } catch (final ReflectiveOperationException e) { - throw new RuntimeException("queue construction failed for factory", e); + } catch (final ReflectiveOperationException error) { + throw new RuntimeException("queue construction failure", error); } } + } - private static class StaticMethodProvidedQueueFactory implements BoundedQueueFactory { + private static final class StaticMethodProvidedQueueFactory implements BoundedQueueFactory { + private final Method method; private StaticMethodProvidedQueueFactory(final Method method) { @@ -190,9 +224,11 @@ private StaticMethodProvidedQueueFactory(final Method method) { public Queue create(final int capacity) { try { return Cast.cast(method.invoke(null, capacity)); - } catch (final ReflectiveOperationException e) { - throw new RuntimeException("queue construction failed for factory", e); + } catch (final ReflectiveOperationException error) { + throw new RuntimeException("queue construction failure", error); } } + } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java index 53a6d6db29e..5fcd4240e4f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/QueueFactory.java @@ -18,6 +18,15 @@ import java.util.Queue; +/** + * A {@link Queue} factory contract. + * + * @see QueueFactories + * @since 3.0.0 + */ +@FunctionalInterface public interface QueueFactory { + Queue create(); + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java index 653877c1f49..2e5105b21c3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringParameterParser.java @@ -16,15 +16,16 @@ */ 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-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 a009dd201ba..da403a010e1 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 @@ -35,6 +35,7 @@ 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.spi.Recycler; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.StringMap; @@ -43,7 +44,7 @@ /** * Mutable implementation of the {@code ReusableLogEvent} interface. * @since 2.6 - * @see org.apache.logging.log4j.spi.Recycler + * @see Recycler */ public class MutableLogEvent implements ReusableLogEvent, ReusableMessage, ParameterVisitable { private static final Message EMPTY = new SimpleMessage(Strings.EMPTY); diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java deleted file mode 100644 index 74771534aaa..00000000000 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/spi/RecyclerFactoriesTest.java +++ /dev/null @@ -1,140 +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.spi; - -import java.lang.reflect.Field; -import java.util.ArrayDeque; -import java.util.concurrent.ArrayBlockingQueue; - -import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; -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.DI; -import org.apache.logging.log4j.plugins.di.Injector; -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 Injector injector = DI.createInjector(); - injector.init(); - // Check if the type converter is registered. - final TypeConverter converter = injector.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(QueueingRecyclerFactory.QueueingRecycler.class); - final QueueingRecyclerFactory.QueueingRecycler queueingRecycler = - (QueueingRecyclerFactory.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(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); - } - - } - - @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 QueueingRecyclerFactory.QueueingRecycler contextRecycler = (QueueingRecyclerFactory.QueueingRecycler) field.get(layout); - final MpmcArrayQueue queue = (MpmcArrayQueue) contextRecycler.getQueue(); - Assertions.assertThat(queue.capacity()).isEqualTo(512); - } - -} 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 4034b1ba3f3..edd8483fe9b 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 @@ -40,10 +40,12 @@ public class SLF4JLogger extends AbstractLogger { */ private static final boolean LAZY_LEVEL_CHECK = "ch.qos.logback.classic.LoggerContext" .equals(LoggerFactory.getILoggerFactory().getClass().getName()); - private static final Recycler logBuilderRecycler = + + 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) { @@ -271,6 +273,11 @@ public void logMessage(final String fqcn, final Level level, final Marker marker } } + @Override + public LogBuilder always() { + return atLevel(Level.OFF); + } + @Override public LogBuilder atTrace() { return atLevel(Level.TRACE); @@ -303,7 +310,7 @@ public LogBuilder atFatal() { @Override protected LogBuilder getLogBuilder(Level level) { - SLF4JLogBuilder builder = logBuilderRecycler.acquire(); + SLF4JLogBuilder builder = LOG_BUILDER_RECYCLER.acquire(); return builder.reset(this, level); } From cd3cac5b196075ae25e09d8930e2b3ecf708b43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Sat, 25 Mar 2023 12:45:32 +0100 Subject: [PATCH 25/39] Merge upstream changes for `SLF4JLogBuilder` --- .../apache/logging/slf4j/SLF4JLogBuilder.java | 229 +++++++++++++----- 1 file changed, 175 insertions(+), 54 deletions(-) diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java index 947391739f6..a910694d683 100644 --- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java +++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java @@ -17,112 +17,233 @@ package org.apache.logging.slf4j; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.internal.DefaultLogBuilder; +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.Supplier; -public class SLF4JLogBuilder extends DefaultLogBuilder { +public class SLF4JLogBuilder implements LogBuilder { - public SLF4JLogBuilder(ExtendedLogger logger, Level level) { - super(logger, level); + private static Message EMPTY_MESSAGE = new SimpleMessage(""); + private static final String FQCN = SLF4JLogBuilder.class.getName(); + private static final Logger LOGGER = StatusLogger.getLogger(); + + private ExtendedLogger logger; + private Level level; + private Marker marker; + private Throwable throwable; + private volatile boolean inUse; + private final long threadId; + + public SLF4JLogBuilder(SLF4JLogger logger, Level level) { + this.logger = logger; + this.level = level; + this.threadId = Thread.currentThread().getId(); + this.inUse = level != null; } public SLF4JLogBuilder() { - super(); + this(null, null); } - @Override - protected boolean isEnabled(Message message) { - // SLF4J will check again later + public LogBuilder reset(SLF4JLogger logger, Level level) { + this.logger = logger; + this.level = level; + this.marker = null; + this.throwable = null; + this.inUse = true; + return this; + } + + public boolean isInUse() { + return this.inUse; + } + + private boolean isValid() { + if (!inUse) { + LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", StackLocatorUtil.getCallerClass(2)); + return false; + } + if (this.threadId != Thread.currentThread().getId()) { + LOGGER.warn("LogBuilder can only be used on the owning thread. {}", StackLocatorUtil.getCallerClass(2)); + return false; + } return true; } + private void logMessage(Message message) { + try { + logger.logMessage(FQCN, level, marker, message, throwable); + } finally { + inUse = false; + } + } + @Override - protected boolean isEnabled(CharSequence message) { - // SLF4J will check again later - return true; + public LogBuilder withMarker(Marker marker) { + this.marker = marker; + return this; } @Override - protected boolean isEnabled(String message) { - // SLF4J will check again later - return true; + public LogBuilder withThrowable(Throwable throwable) { + this.throwable = throwable; + return this; } @Override - protected boolean isEnabled(String message, Object... params) { - // SLF4J will check again later - return true; + public LogBuilder withLocation() { + LOGGER.info("Call to withLocation() ignored since SLF4J does not support setting location information."); + return this; } @Override - protected boolean isEnabled(Object message) { - // SLF4J will check again later - return true; + public LogBuilder withLocation(StackTraceElement location) { + return withLocation(); } @Override - protected boolean isEnabled(String message, Object p0) { - // SLF4J will check again later - return true; + public void log(CharSequence message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1) { - // SLF4J will check again later - return true; + public void log(String message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2) { - // SLF4J will check again later - return true; + public void log(String message, Object... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, params)); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3) { - // SLF4J will check again later - return true; + public void log(String message, Supplier... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params))); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { - // SLF4J will check again later - return true; + public void log(Message message) { + if (isValid()) { + logMessage(message); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { - // SLF4J will check again later - return true; + public void log(Supplier messageSupplier) { + if (isValid()) { + logMessage(messageSupplier.get()); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6) { - // SLF4J will check again later - return true; + public Message logAndGet(Supplier messageSupplier) { + Message message = null; + if (isValid()) { + logMessage(message = messageSupplier.get()); + } + return message; } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6, Object p7) { - // SLF4J will check again later - return true; + public void log(Object message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6, Object p7, Object p8) { - // SLF4J will check again later - return true; + public void log(String message, Object p0) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0)); + } } @Override - protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6, Object p7, Object p8, Object p9) { - // SLF4J will check again later - return true; + public void log(String message, Object p0, Object p1) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + Object p7) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + Object p7, Object p8) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + Object p7, Object p8, Object p9) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); + } + } + + @Override + public void log() { + if (isValid()) { + logMessage(EMPTY_MESSAGE); + } } } From 7299ef4ca6b61c0ad80664a45a6440b4192c8794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Sat, 25 Mar 2023 13:29:30 +0100 Subject: [PATCH 26/39] More changes --- .../impl/ReusableLogEventFactoryTest.java | 8 +-- .../core/layout/AbstractStringLayoutTest.java | 60 +++++++++++-------- .../logging/log4j/core/layout/GelfLayout.java | 53 +++++----------- .../log4j/core/layout/PatternLayout.java | 42 ++++++++++--- .../log4j/core/layout/Rfc5424Layout.java | 36 +++++------ .../logging/log4j/core/util/Constants.java | 2 +- .../template/json/JsonTemplateLayout.java | 55 +++++------------ .../json/JsonTemplateLayoutDefaults.java | 14 +---- .../json/resolver/CounterResolver.java | 1 + .../json/resolver/EventResolverContext.java | 26 ++------ .../resolver/MessageParameterResolver.java | 1 + .../resolver/ReadOnlyStringMapResolver.java | 2 +- .../resolver/StackTraceStringResolver.java | 2 +- 13 files changed, 135 insertions(+), 167 deletions(-) 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 4769cf652a5..f0d8c346bc9 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 @@ -42,7 +42,7 @@ void setUp() { } @Test - public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { + 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); @@ -51,7 +51,7 @@ public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Except } @Test - public void testCreateEventReturnsSameInstance() throws Exception { + 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); @@ -64,7 +64,7 @@ public void testCreateEventReturnsSameInstance() throws Exception { } @Test - public void testCreateEventOverwritesFields() throws Exception { + 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"); @@ -125,7 +125,7 @@ public void run() { } @Test - public void testCreateEventInitFieldsProperly() throws Exception { + public void testCreateEventInitFieldsProperly() { final LogEvent event = callCreateEvent("logger", Level.INFO, new SimpleMessage("xyz"), null); try { assertNotNull(event.getContextData()); 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 257a291b167..175bacc9ae5 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 @@ -43,39 +43,51 @@ public String toSerializable(final LogEvent event) { } @Test - public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { + public void testGetStringBuilderCapacityRestrictedToMax() { + final ConcreteStringLayout layout = new ConcreteStringLayout(); final StringBuilder sb = layout.acquireStringBuilder(); final int initialCapacity = sb.capacity(); - assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); + 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"); - layout.releaseStringBuilder(sb); + 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.releaseStringBuilder(sb); + } final StringBuilder sb2 = layout.acquireStringBuilder(); - assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); - assertEquals(0, sb2.length(), "empty, ready for use"); + 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"); - layout.releaseStringBuilder(sb2); + 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.releaseStringBuilder(sb2); + } final StringBuilder sb3 = layout.acquireStringBuilder(); - assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(), - "capacity, trimmed to MAX_STRING_BUILDER_SIZE"); - assertEquals(0, sb3.length(), "empty, ready for use"); + 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.releaseStringBuilder(sb3); + } + } } 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 69baf2f181f..bd911e8e8b4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java @@ -16,19 +16,6 @@ */ package org.apache.logging.log4j.core.layout; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.GZIPOutputStream; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -38,28 +25,26 @@ 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.StringBuilderWriter; +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.Inject; -import org.apache.logging.log4j.plugins.Plugin; -import org.apache.logging.log4j.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.plugins.PluginElement; -import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + /** * Lays out events in the Graylog Extended Log Format (GELF) 1.1. *

@@ -176,8 +161,6 @@ public static class Builder> extends AbstractStringLayout.B @PluginElement("PatternSelector") private PatternSelector patternSelector = null; - private RecyclerFactory recyclerFactory; - public Builder() { super(); setCharset(StandardCharsets.UTF_8); @@ -206,13 +189,10 @@ public GelfLayout build() { .setConfiguration(config) .build(); } - if (recyclerFactory == null) { - recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); - } return new GelfLayout(config, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter, includeNewLineDelimiter, omitEmptyFields, mdcChecker, mapChecker, patternLayout, - threadContextPrefix, mapPrefix, recyclerFactory); + threadContextPrefix, mapPrefix); } private ListChecker createChecker(final String excludes, final String includes) { @@ -449,11 +429,6 @@ public B setMapPrefix(final String prefix) { return asBuilder(); } - @Inject - public B setRecyclerFactory(final RecyclerFactory recyclerFactory) { - this.recyclerFactory = recyclerFactory; - return asBuilder(); - } } private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, @@ -461,7 +436,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue final boolean includeThreadContext, final boolean includeMapMessage, final boolean includeNullDelimiter, final boolean includeNewLineDelimiter, final boolean omitEmptyFields, final ListChecker mdcChecker, final ListChecker mapChecker, final PatternLayout patternLayout, final String mdcPrefix, - final String mapPrefix, final RecyclerFactory recyclerFactory) { + final String mapPrefix) { super(config, StandardCharsets.UTF_8, null, null); this.host = host != null ? host : NetUtils.getLocalHostname(); this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0]; @@ -486,7 +461,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue this.mdcWriter = new FieldWriter(mdcChecker, mdcPrefix); this.mapWriter = new FieldWriter(mapChecker, mapPrefix); this.layout = patternLayout; - stacktraceRecycler = recyclerFactory.create( + stacktraceRecycler = config.getRecyclerFactory().create( () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE), writer -> { final StringBuilder stringBuilder = writer.getBuilder(); 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 2f365b47a4b..b34f25c2492 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 @@ -106,9 +106,17 @@ public final class PatternLayout extends AbstractStringLayout { * @param headerPattern header conversion pattern. * @param footerPattern footer conversion pattern. */ - private PatternLayout(final Configuration config, final RegexReplacement replace, final String eventPattern, - final PatternSelector patternSelector, final Charset charset, final boolean alwaysWriteExceptions, - final boolean disableAnsi, final boolean noConsoleNoAnsi, final String headerPattern, + private PatternLayout( + final Configuration config, + final RecyclerFactory recyclerFactory, + final RegexReplacement replace, + final String eventPattern, + final PatternSelector patternSelector, + final Charset charset, + final boolean alwaysWriteExceptions, + final boolean disableAnsi, + final boolean noConsoleNoAnsi, + final String headerPattern, final String footerPattern) { super(config, charset, newSerializerBuilder() @@ -371,6 +379,7 @@ public boolean requiresLocation() { public static class SerializerBuilder implements org.apache.logging.log4j.plugins.util.Builder { private Configuration configuration; + private RecyclerFactory recyclerFactory; private RegexReplacement replace; private String pattern; private String defaultPattern; @@ -384,9 +393,11 @@ public Serializer build() { if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) { return null; } - final RecyclerFactory recyclerFactory = configuration != null - ? configuration.getRecyclerFactory() - : LoggingSystem.getRecyclerFactory(); + if (recyclerFactory == null) { + recyclerFactory = configuration != null + ? configuration.getRecyclerFactory() + : LoggingSystem.getRecyclerFactory(); + } final Recycler recycler = createRecycler(recyclerFactory); if (patternSelector == null) { try { @@ -420,6 +431,11 @@ public SerializerBuilder setConfiguration(final Configuration configuration) { return this; } + public SerializerBuilder setRecyclerFactory(final RecyclerFactory recyclerFactory) { + this.recyclerFactory = recyclerFactory; + return this; + } + public SerializerBuilder setReplace(final RegexReplacement replace) { this.replace = replace; return this; @@ -561,6 +577,9 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui @PluginConfiguration private Configuration configuration; + @PluginBuilderAttribute + private RecyclerFactory recyclerFactory; + @PluginElement("Replace") private RegexReplacement regexReplacement; @@ -620,6 +639,14 @@ public Builder setConfiguration(final Configuration configuration) { return this; } + /** + * @param recyclerFactory a recycler factory + */ + public Builder setRecyclerFactory(final RecyclerFactory recyclerFactory) { + this.recyclerFactory = recyclerFactory; + return this; + } + /** * @param regexReplacement * A Regex replacement @@ -693,7 +720,8 @@ public PatternLayout build() { if (configuration == null) { configuration = new DefaultConfiguration(); } - return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset, + return new PatternLayout( + configuration, recyclerFactory, regexReplacement, pattern, patternSelector, charset, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, header, footer); } } 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 23bfacf544b..71f8497f65d 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 @@ -594,25 +594,25 @@ public String toString() { /** * Create the RFC 5424 Layout. * - * @param facility The Facility is used to try to classify the message. - * @param id The default structured data id to use when formatting according to RFC 5424. - * @param enterpriseNumber The IANA enterprise number. - * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog - * record. Defaults to "true:. - * @param mdcId The id to use for the MDC Structured Data Element. - * @param mdcPrefix The prefix to add to MDC key names. - * @param eventPrefix The prefix to add to event key names. - * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. - * @param escapeNL String that should be used to replace newlines within the message text. - * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. - * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. - * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. - * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. - * @param required A comma separated list of MDC keys that must be present in the MDC. - * @param exceptionPattern The pattern for formatting exceptions. + * @param facility The Facility is used to try to classify the message. + * @param id The default structured data id to use when formatting according to RFC 5424. + * @param enterpriseNumber The IANA enterprise number. + * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog + * record. Defaults to "true:. + * @param mdcId The id to use for the MDC Structured Data Element. + * @param mdcPrefix The prefix to add to MDC key names. + * @param eventPrefix The prefix to add to event key names. + * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. + * @param escapeNL String that should be used to replace newlines within the message text. + * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. + * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. + * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. + * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. + * @param required A comma separated list of MDC keys that must be present in the MDC. + * @param exceptionPattern The pattern for formatting exceptions. * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425. - * @param loggerFields Container for the KeyValuePairs containing the patterns - * @param config The Configuration. Some Converters require access to the Interpolator. + * @param loggerFields Container for the KeyValuePairs containing the patterns + * @param config The Configuration. Some Converters require access to the Interpolator. * @return An Rfc5424Layout. * @deprecated Use {@link Rfc5424LayoutBuilder instead} */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java index 578da7717d4..62fc657e368 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Constants.java @@ -112,7 +112,7 @@ private static boolean isJndiEnabled(final String subKey) { * Maximum size of the StringBuilders used in RingBuffer LogEvents to store the contents of reusable Messages. * After a large message has been delivered to the appenders, the StringBuilder is trimmed to this size. *

- * The default value is 518, which allows the StringBuilder to resize three times from its initial size. + * The default value is {@value}, which allows the StringBuilder to resize three times from its initial size. * Users can override with system property "log4j.maxReusableMsgSize". *

* @since 2.6 diff --git a/log4j-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 1bd4895de16..a9edfb8b76d 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,16 +16,6 @@ */ package org.apache.logging.log4j.layout.template.json; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; @@ -42,10 +32,19 @@ import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + @Configurable(elementType = Layout.ELEMENT_TYPE) @Plugin public class JsonTemplateLayout implements StringLayout { @@ -94,11 +93,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); } @@ -149,7 +144,6 @@ private TemplateResolver createEventResolver( .setSubstitutor(substitutor) .setCharset(charset) .setJsonWriter(jsonWriter) - .setRecyclerFactory(builder.recyclerFactory) .setMaxStringByteCount(maxStringByteCount) .setTruncatedStringSuffix(builder.truncatedStringSuffix) .setLocationInfoEnabled(builder.locationInfoEnabled) @@ -187,14 +181,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( @@ -393,10 +382,6 @@ public static final class Builder private String truncatedStringSuffix = JsonTemplateLayoutDefaults.getTruncatedStringSuffix(); - @PluginBuilderAttribute - private RecyclerFactory recyclerFactory = - JsonTemplateLayoutDefaults.getRecyclerFactory(); - private Builder() { // Do nothing. } @@ -531,15 +516,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(); @@ -547,7 +523,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"); @@ -564,7 +540,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 db77071681c..962853fa81b 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 @@ -16,16 +16,14 @@ */ package org.apache.logging.log4j.layout.template.json; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; + import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.TimeZone; -import org.apache.logging.log4j.spi.RecyclerFactories; -import org.apache.logging.log4j.spi.RecyclerFactory; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.PropertyEnvironment; - public final class JsonTemplateLayoutDefaults { private JsonTemplateLayoutDefaults() {} @@ -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 139a699f19f..39892ee8802 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 @@ -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 28038ae81f9..aa5cf042615 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java @@ -16,18 +16,17 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.Strings; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Objects; + /** * {@link TemplateResolverContext} specialized for {@link LogEvent}s. * @@ -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 3dc80ad16a8..da935e0419c 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 @@ -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 7ea4730186f..b5065d46f2c 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 @@ -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 c02f8f11b4d..0b1a890d494 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 @@ -56,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); From 623c47c7ecd9eb12c2597181bfe40af13db2a7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 28 Mar 2023 19:55:18 +0200 Subject: [PATCH 27/39] More `ThreadLocal`-to-recycler refactoring --- .../log4j/layout/Log4j1SyslogLayout.java | 18 ++- .../apache/log4j/layout/Log4j1XmlLayout.java | 25 ++-- .../core/layout/AbstractStringLayoutTest.java | 15 +-- .../org/apache/logging/log4j/core/Layout.java | 1 + .../log4j/core/layout/AbstractLayout.java | 70 +++++++++-- .../core/layout/AbstractStringLayout.java | 119 ++++++------------ .../logging/log4j/core/layout/GelfLayout.java | 49 ++++---- .../logging/log4j/core/layout/HtmlLayout.java | 45 ++++--- .../layout/LockingStringBuilderEncoder.java | 13 +- .../log4j/core/layout/PatternLayout.java | 26 ++-- .../log4j/core/layout/Rfc5424Layout.java | 6 +- .../core/layout/StringBuilderEncoder.java | 84 ++++++------- .../log4j/core/layout/SyslogLayout.java | 16 ++- .../log4j/core/layout/TextEncoderHelper.java | 6 - .../log4j/csv/layout/CsvLogEventLayout.java | 4 +- .../log4j/csv/layout/CsvParameterLayout.java | 4 +- .../template/json/JsonTemplateLayout.java | 68 ++-------- .../json/JsonTemplateLayoutDefaults.java | 6 +- .../json/resolver/EventResolverContext.java | 10 +- ...ctStringLayoutStringEncodingBenchmark.java | 21 ++-- 20 files changed, 293 insertions(+), 313 deletions(-) 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 0787dbbd455..b97a7a16d35 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; @@ -83,7 +84,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() { @@ -145,9 +146,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; @@ -166,7 +172,7 @@ 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 = acquireStringBuilder(); + final StringBuilder buf = stringBuilderRecycler.acquire(); try { buf.append('<'); @@ -194,7 +200,7 @@ public String toSerializable(final LogEvent event) { // TODO: splitting message into 1024 byte chunks? return buf.toString(); } finally { - releaseStringBuilder(buf); + 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 4f48fdd0912..0055df3fb7f 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 @@ -24,8 +24,11 @@ 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; @@ -52,15 +55,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; } @@ -75,23 +79,28 @@ public boolean isProperties() { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder text = acquireStringBuilder(); + final StringBuilder text = stringBuilderRecycler.acquire(); try { formatTo(event, text); - getStringBuilderEncoder().encode(text, destination); + final Encoder stringBuilderEncoder = stringBuilderEncoderRecycler.acquire(); + try { + stringBuilderEncoder.encode(text, destination); + } finally { + stringBuilderEncoderRecycler.release(stringBuilderEncoder); + } } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } } @Override public String toSerializable(final LogEvent event) { - final StringBuilder text = acquireStringBuilder(); + final StringBuilder text = stringBuilderRecycler.acquire(); try { formatTo(event, text); return text.toString(); } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } } 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 175bacc9ae5..0827a4b082a 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,6 +19,7 @@ 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.assertEquals; @@ -33,7 +34,7 @@ static class ConcreteStringLayout extends AbstractStringLayout { public static int MAX_STRING_BUILDER_SIZE = AbstractStringLayout.MAX_STRING_BUILDER_SIZE; public ConcreteStringLayout() { - super(Charset.defaultCharset()); + super(new DefaultConfiguration(), Charset.defaultCharset()); } @Override @@ -46,7 +47,7 @@ public String toSerializable(final LogEvent event) { public void testGetStringBuilderCapacityRestrictedToMax() { final ConcreteStringLayout layout = new ConcreteStringLayout(); - final StringBuilder sb = layout.acquireStringBuilder(); + final StringBuilder sb = layout.stringBuilderRecycler.acquire(); final int initialCapacity = sb.capacity(); try { assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); @@ -57,10 +58,10 @@ public void testGetStringBuilderCapacityRestrictedToMax() { assertEquals(initialCapacity, sb.capacity(), "capacity not grown"); assertEquals(SMALL, sb.length(), "length=msg length"); } finally { - layout.releaseStringBuilder(sb); + layout.stringBuilderRecycler.release(sb); } - final StringBuilder sb2 = layout.acquireStringBuilder(); + final StringBuilder sb2 = layout.stringBuilderRecycler.acquire(); try { assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); assertEquals(0, sb2.length(), "empty, ready for use"); @@ -76,16 +77,16 @@ public void testGetStringBuilderCapacityRestrictedToMax() { assertEquals(0, sb2.length(), "empty, cleared"); assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, "capacity remains very large"); } finally { - layout.releaseStringBuilder(sb2); + layout.stringBuilderRecycler.release(sb2); } - final StringBuilder sb3 = layout.acquireStringBuilder(); + 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.releaseStringBuilder(sb3); + layout.stringBuilderRecycler.release(sb3); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java index c2308bdb1ad..b833b060de4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java @@ -97,4 +97,5 @@ default Charset getCharset() { default boolean requiresLocation() { return false; } + } 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 a0fdc153a16..34a595e33b3 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,8 +16,11 @@ */ package org.apache.logging.log4j.core.layout; -import java.util.HashMap; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Layout; @@ -33,6 +36,8 @@ */ public abstract class AbstractLayout implements Layout { + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + /** * Subclasses can extend this abstract Builder. * @@ -43,6 +48,9 @@ public abstract static class Builder> { @PluginConfiguration private Configuration configuration; + @PluginBuilderAttribute + private Charset charset; + @PluginBuilderAttribute private byte[] footer; @@ -57,6 +65,10 @@ public Configuration getConfiguration() { return configuration; } + public Charset getCharset() { + return charset; + } + public byte[] getFooter() { return footer; } @@ -70,6 +82,11 @@ public B setConfiguration(final Configuration configuration) { return asBuilder(); } + public B setCharset(final Charset charset) { + this.charset = charset; + return asBuilder(); + } + public B setFooter(final byte[] footer) { this.footer = footer; return asBuilder(); @@ -92,6 +109,11 @@ public B setHeader(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. */ @@ -107,19 +129,38 @@ public B setHeader(final byte[] header) { */ protected final byte[] header; + /** + * Constructs a UTF-8 encoded layout with an optional header and footer. + * + * @param configuration a configuration + * @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 + * @deprecated use {@link AbstractLayout#AbstractLayout(Configuration, Charset, byte[], byte[])} instead + */ + @Deprecated + public AbstractLayout( + final Configuration configuration, + final byte[] header, + final byte[] footer) { + this(configuration, DEFAULT_CHARSET, header, footer); + } + /** * 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; } @@ -128,9 +169,14 @@ public Configuration getConfiguration() { return configuration; } + @Override + public Charset getCharset() { + return charset; + } + @Override public Map getContentFormat() { - return new HashMap<>(); + return Collections.emptyMap(); } /** @@ -189,13 +235,11 @@ protected void markEvent() { * * @param event the LogEvent to encode. * @param destination holds the ByteBuffer to write into. - * @see AbstractStringLayout#acquireStringBuilder() - * @see AbstractStringLayout#releaseStringBuilder(StringBuilder) - * @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 55572b4252d..dee942e936e 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,7 +17,6 @@ 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; @@ -25,11 +24,8 @@ import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.impl.LogEventFactory; -import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.StringEncoder; -import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginElement; -import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Recycler; import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.util.PropertiesUtil; @@ -47,19 +43,12 @@ public abstract class AbstractStringLayout extends AbstractLayout implements Str public abstract static class Builder> extends AbstractLayout.Builder { - @PluginBuilderAttribute(value = "charset") - private Charset charset; - @PluginElement("footerSerializer") private Serializer footerSerializer; @PluginElement("headerSerializer") private Serializer headerSerializer; - public Charset getCharset() { - return charset; - } - public Serializer getFooterSerializer() { return footerSerializer; } @@ -68,11 +57,6 @@ public Serializer getHeaderSerializer() { return headerSerializer; } - public B setCharset(final Charset charset) { - this.charset = charset; - return asBuilder(); - } - public B setFooterSerializer(final Serializer footerSerializer) { this.footerSerializer = footerSerializer; return asBuilder(); @@ -86,6 +70,7 @@ public B setHeaderSerializer(final Serializer headerSerializer) { } public interface Serializer extends Serializer2 { + String toSerializable(final LogEvent event); default boolean requiresLocation() { @@ -97,6 +82,7 @@ default StringBuilder toSerializable(final LogEvent event, final StringBuilder b builder.append(toSerializable(event)); return builder; } + } /** @@ -113,14 +99,15 @@ 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(Log4jProperties.GC_LAYOUT_STRING_BUILDER_MAX_SIZE, 2 * 1024)); + protected static final int MAX_STRING_BUILDER_SIZE = Math.max( + DEFAULT_STRING_BUILDER_SIZE, + PropertiesUtil + .getProperties() + .getIntegerProperty( + Log4jProperties.GC_LAYOUT_STRING_BUILDER_MAX_SIZE, + Math.multiplyExact(2, DEFAULT_STRING_BUILDER_SIZE))); - private static int size(final String property, final int defaultValue) { - return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); - } - - protected static Recycler createRecycler(final RecyclerFactory recyclerFactory) { + protected static Recycler createStringBuilderRecycler(final RecyclerFactory recyclerFactory) { return recyclerFactory.create( () -> new StringBuilder(DEFAULT_STRING_BUILDER_SIZE), stringBuilder -> { @@ -130,58 +117,61 @@ protected static Recycler createRecycler(final RecyclerFactory re ); } - private Encoder textEncoder; - /** - * The charset for the formatted message. - */ - private final Charset charset; - private final Serializer footerSerializer; private final Serializer headerSerializer; - private final RecyclerFactory recyclerFactory; + protected final Recycler> stringBuilderEncoderRecycler; - private final Recycler recycler; + protected final Recycler stringBuilderRecycler; - protected AbstractStringLayout(final Charset charset) { - this(charset, (byte[]) null, (byte[]) null); + 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; - recyclerFactory = LoggingSystem.getRecyclerFactory(); - recycler = createRecycler(recyclerFactory); + 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 the 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; - recyclerFactory = config != null ? config.getRecyclerFactory() : LoggingSystem.getRecyclerFactory(); - recycler = createRecycler(recyclerFactory); + 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) { @@ -229,32 +219,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; - } - - /** - * Returns a StringBuilder that may be recycled via {@link #releaseStringBuilder(StringBuilder)} when done being used. - */ - protected StringBuilder acquireStringBuilder() { - return recycler.acquire(); - } - - /** - * Recycles a StringBuilder acquired via {@link #acquireStringBuilder()} so that it may be acquired again later. - */ - protected void releaseStringBuilder(final StringBuilder stringBuilder) { - recycler.release(stringBuilder); - } - protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { final String serializable = serializeToString(serializer); if (serializable == null) { @@ -289,7 +253,4 @@ public byte[] toByteArray(final LogEvent event) { @Override public abstract String toSerializable(LogEvent event); - public RecyclerFactory getRecyclerFactory() { - return recyclerFactory; - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java index bd911e8e8b4..eb43cd551ef 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java @@ -16,6 +16,15 @@ */ package org.apache.logging.log4j.core.layout; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -36,15 +45,6 @@ import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.GZIPOutputStream; - /** * Lays out events in the Graylog Extended Log Format (GELF) 1.1. *

@@ -437,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) { @@ -512,12 +512,12 @@ public String getContentType() { @Override public byte[] toByteArray(final LogEvent event) { - final StringBuilder text = acquireStringBuilder(); + final StringBuilder text = stringBuilderRecycler.acquire(); final byte[] bytes; try { bytes = getBytes(toText(event, text, false).toString()); } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } return compressionType != CompressionType.OFF && bytes.length > compressionThreshold ? compress(bytes) : bytes; } @@ -528,12 +528,17 @@ public void encode(final LogEvent event, final ByteBufferDestination destination super.encode(event, destination); return; } - final StringBuilder text = acquireStringBuilder(); + final StringBuilder text = stringBuilderRecycler.acquire(); try { - final Encoder helper = getStringBuilderEncoder(); - helper.encode(toText(event, text, true), destination); + final StringBuilder encodedEvent = toText(event, text, true); + final Encoder helper = stringBuilderEncoderRecycler.acquire(); + try { + helper.encode(encodedEvent, destination); + } finally { + stringBuilderEncoderRecycler.release(helper); + } } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } } @@ -561,11 +566,11 @@ private byte[] compress(final byte[] bytes) { @Override public String toSerializable(final LogEvent event) { - final StringBuilder text = acquireStringBuilder(); + final StringBuilder text = stringBuilderRecycler.acquire(); try { return toText(event, text, false).toString(); } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } } @@ -614,12 +619,12 @@ 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 = acquireStringBuilder(); + final StringBuilder messageBuffer = stringBuilderRecycler.acquire(); try { layout.serialize(event, messageBuffer); JsonUtils.quoteAsString(messageBuffer, builder); } finally { - releaseStringBuilder(messageBuffer); + stringBuilderRecycler.release(messageBuffer); } } else { if (includeStacktrace) { @@ -642,12 +647,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 = acquireStringBuilder(); + final StringBuilder messageBuffer = stringBuilderRecycler.acquire(); try { ((StringBuilderFormattable) message).formatTo(messageBuffer); JsonUtils.quoteAsString(messageBuffer, builder); } finally { - releaseStringBuilder(messageBuffer); + stringBuilderRecycler.release(messageBuffer); } } else { JsonUtils.quoteAsString(toNullSafeString(message.getFormattedMessage()), builder); 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 74afc2db8f3..3401ec89859 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 @@ -31,7 +31,9 @@ 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.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; @@ -102,9 +104,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); @@ -149,7 +160,7 @@ private String addCharsetToContentType(final String contentType) { */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder sbuf = acquireStringBuilder(); + final StringBuilder sbuf = stringBuilderRecycler.acquire(); try { sbuf.append(Strings.LINE_SEPARATOR).append("").append(Strings.LINE_SEPARATOR); @@ -230,14 +241,11 @@ public String toSerializable(final LogEvent event) { return sbuf.toString(); } finally { - releaseStringBuilder(sbuf); + stringBuilderRecycler.release(sbuf); } } @Override - /** - * @return The content type. - */ public String getContentType() { return contentType; } @@ -293,7 +301,7 @@ private StringBuilder append(final StringBuilder sbuilder, final String s) { */ @Override public byte[] getHeader() { - final StringBuilder sbuf = acquireStringBuilder(); + final StringBuilder sbuf = stringBuilderRecycler.acquire(); try { append(sbuf, ""); @@ -330,7 +338,7 @@ public byte[] getHeader() { appendLs(sbuf, ""); return sbuf.toString().getBytes(getCharset()); } finally { - releaseStringBuilder(sbuf); + stringBuilderRecycler.release(sbuf); } } @@ -340,14 +348,14 @@ public byte[] getHeader() { */ @Override public byte[] getFooter() { - final StringBuilder sbuf = acquireStringBuilder(); + final StringBuilder sbuf = stringBuilderRecycler.acquire(); try { appendLs(sbuf, ""); appendLs(sbuf, "
"); appendLs(sbuf, ""); return getBytes(sbuf.toString()); } finally { - releaseStringBuilder(sbuf); + stringBuilderRecycler.release(sbuf); } } @@ -367,6 +375,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; @@ -394,6 +405,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; @@ -440,8 +456,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 5bb9a0aacf9..666ad84d910 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 @@ -30,6 +30,8 @@ */ public class LockingStringBuilderEncoder implements Encoder { + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + private final Charset charset; private final CharsetEncoder charsetEncoder; private final CharBuffer cachedCharBuffer; @@ -57,15 +59,12 @@ public void encode(final StringBuilder source, final ByteBufferDestination desti TextEncoderHelper.encodeText(charsetEncoder, cachedCharBuffer, destination.getByteBuffer(), source, destination); } - } catch (final Exception ex) { - logEncodeTextException(ex, source, destination); - TextEncoderHelper.encodeTextFallBack(charset, source, destination); + } catch (final Exception error) { + LOGGER.error("Due to `TextEncoderHelper.encodeText()` failure, falling back to `String#getBytes(Charset)`", error); + byte[] sourceBytes = source.toString().getBytes(charset); + destination.writeBytes(sourceBytes, 0, sourceBytes.length); } } - 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 b34f25c2492..a1840c8b018 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 @@ -205,14 +205,18 @@ public void serialize(final LogEvent event, final StringBuilder stringBuilder) { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder builder = acquireStringBuilder(); + final StringBuilder builder = stringBuilderRecycler.acquire(); StringBuilder text = builder; try { text = eventSerializer.toSerializable(event, builder); - final Encoder encoder = getStringBuilderEncoder(); - encoder.encode(text, destination); + final Encoder encoder = stringBuilderEncoderRecycler.acquire(); + try { + encoder.encode(text, destination); + } finally { + stringBuilderEncoderRecycler.release(encoder); + } } finally { - releaseStringBuilder(text); + stringBuilderRecycler.release(text); } } @@ -379,7 +383,6 @@ public boolean requiresLocation() { public static class SerializerBuilder implements org.apache.logging.log4j.plugins.util.Builder { private Configuration configuration; - private RecyclerFactory recyclerFactory; private RegexReplacement replace; private String pattern; private String defaultPattern; @@ -393,12 +396,7 @@ public Serializer build() { if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) { return null; } - if (recyclerFactory == null) { - recyclerFactory = configuration != null - ? configuration.getRecyclerFactory() - : LoggingSystem.getRecyclerFactory(); - } - final Recycler recycler = createRecycler(recyclerFactory); + final Recycler recycler = createStringBuilderRecycler(configuration.getRecyclerFactory()); if (patternSelector == null) { try { final PatternParser parser = createPatternParser(configuration); @@ -431,11 +429,6 @@ public SerializerBuilder setConfiguration(final Configuration configuration) { return this; } - public SerializerBuilder setRecyclerFactory(final RecyclerFactory recyclerFactory) { - this.recyclerFactory = recyclerFactory; - return this; - } - public SerializerBuilder setReplace(final RegexReplacement replace) { this.replace = replace; return this; @@ -603,6 +596,7 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui private String footer; private Builder() { + setCharset(Charset.defaultCharset()); // LOG4J2-783 Default should not be UTF-8 } private boolean useAnsiEscapeCodes() { 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 71f8497f65d..a9846012cca 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 @@ -137,7 +137,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; @@ -274,7 +274,7 @@ public Map getContentFormat() { */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder buf = acquireStringBuilder(); + final StringBuilder buf = stringBuilderRecycler.acquire(); try { appendPriority(buf, event.getLevel()); appendTimestamp(buf, event.getTimeMillis()); @@ -294,7 +294,7 @@ public String toSerializable(final LogEvent event) { } return buf.toString(); } finally { - releaseStringBuilder(buf); + stringBuilderRecycler.release(buf); } } 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 9af331634f9..6ddf39112c7 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,65 @@ import java.util.Objects; import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.spi.RecyclerFactory; import org.apache.logging.log4j.status.StatusLogger; /** - * 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 static final StatusLogger LOGGER = StatusLogger.getLogger(); + + 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) { + LOGGER.error("Due to `TextEncoderHelper.encodeText()` failure, falling back to `String#getBytes(Charset)`", error); + byte[] sourceBytes = source.toString().getBytes(charset); + destination.writeBytes(sourceBytes, 0, sourceBytes.length); + } 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 be88b18009d..c854809607d 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 @@ -28,6 +28,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; @@ -74,7 +75,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() { @@ -130,8 +131,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); @@ -145,7 +151,7 @@ protected SyslogLayout(final Facility facility, final boolean includeNL, final S */ @Override public String toSerializable(final LogEvent event) { - final StringBuilder buf = acquireStringBuilder(); + final StringBuilder buf = stringBuilderRecycler.acquire(); try { buf.append('<'); @@ -167,7 +173,7 @@ public String toSerializable(final LogEvent event) { } return buf.toString(); } finally { - releaseStringBuilder(buf); + stringBuilderRecycler.release(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 4191f571212..2b5d643fb97 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 @@ -33,12 +33,6 @@ public class TextEncoderHelper { private TextEncoderHelper() { } - static void encodeTextFallBack(final Charset charset, final StringBuilder text, - final ByteBufferDestination destination) { - final byte[] bytes = text.toString().getBytes(charset); - destination.writeBytes(bytes, 0, bytes.length); - } - /** * Converts the specified text to bytes and writes the resulting bytes to the specified destination. * Attempts to postpone synchronizing on the destination as long as possible to minimize lock contention. 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 216af9a7f13..e755bc3fbc7 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 @@ -77,7 +77,7 @@ protected CsvLogEventLayout(final Configuration config, final Charset charset, f @Override public String toSerializable(final LogEvent event) { - final StringBuilder buffer = acquireStringBuilder(); + final StringBuilder buffer = stringBuilderRecycler.acquire(); final CSVFormat format = getFormat(); try { format.print(event.getNanoTime(), buffer, true); @@ -100,7 +100,7 @@ public String toSerializable(final LogEvent event) { StatusLogger.getLogger().error(event.toString(), e); return format.getCommentMarker() + " " + e; } finally { - releaseStringBuilder(buffer); + 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 cd83093fa0f..c176c066f84 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 @@ -88,7 +88,7 @@ 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 = acquireStringBuilder(); + final StringBuilder buffer = stringBuilderRecycler.acquire(); try { getFormat().printRecord(buffer, parameters); return buffer.toString(); @@ -96,7 +96,7 @@ public String toSerializable(final LogEvent event) { StatusLogger.getLogger().error(message, e); return getFormat().getCommentMarker() + " " + e; } finally { - releaseStringBuilder(buffer); + stringBuilderRecycler.release(buffer); } } 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 a9edfb8b76d..51f3b89fce3 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java @@ -16,6 +16,12 @@ */ package org.apache.logging.log4j.layout.template.json; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.StringLayout; @@ -23,8 +29,7 @@ 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.core.util.StringEncoder; import org.apache.logging.log4j.layout.template.json.resolver.*; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; @@ -32,19 +37,8 @@ import org.apache.logging.log4j.plugins.*; import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.spi.Recycler; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - @Configurable(elementType = Layout.ELEMENT_TYPE) @Plugin public class JsonTemplateLayout implements StringLayout { @@ -196,54 +190,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); 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 962853fa81b..ddec6703d16 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 @@ -16,14 +16,14 @@ */ package org.apache.logging.log4j.layout.template.json; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.PropertyEnvironment; - import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.TimeZone; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; + public final class JsonTemplateLayoutDefaults { private JsonTemplateLayoutDefaults() {} 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 aa5cf042615..1b3430b5135 100644 --- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java +++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java @@ -16,17 +16,17 @@ */ package org.apache.logging.log4j.layout.template.json.resolver; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Objects; + import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; import org.apache.logging.log4j.util.Strings; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Objects; - /** * {@link TemplateResolverContext} specialized for {@link LogEvent}s. * diff --git a/log4j-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 1020250bcd9..f38613b5fd8 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,6 +24,7 @@ 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; @@ -204,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 @@ -214,19 +215,19 @@ public String toSerializable(final LogEvent event) { @Override public byte[] toByteArray(final LogEvent event) { - final StringBuilder sb = acquireStringBuilder(); + final StringBuilder sb = stringBuilderRecycler.acquire(); try { ((StringBuilderFormattable) event.getMessage()).formatTo(sb); return getBytes(sb.toString()); } finally { - releaseStringBuilder(sb); + stringBuilderRecycler.release(sb); } } } private static class EncodeLayout extends AbstractStringLayout { public EncodeLayout(final Charset charset) { - super(charset); + super(new DefaultConfiguration(), charset); } @Override @@ -241,13 +242,17 @@ public byte[] toByteArray(final LogEvent event) { @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - final StringBuilder sb = acquireStringBuilder(); + final StringBuilder sb = stringBuilderRecycler.acquire(); try { ((StringBuilderFormattable) event.getMessage()).formatTo(sb); - final Encoder helper = getStringBuilderEncoder(); - helper.encode(sb, destination); + final Encoder helper = stringBuilderEncoderRecycler.acquire(); + try { + helper.encode(sb, destination); + } finally { + stringBuilderEncoderRecycler.release(helper); + } } finally { - releaseStringBuilder(sb); + stringBuilderRecycler.release(sb); } } } From 1ec22cb82813b41f5f687fc0d435414d17aa2b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 28 Mar 2023 22:01:01 +0200 Subject: [PATCH 28/39] More refactoring --- .../builders/layout/XmlLayoutBuilder.java | 9 ++--- .../log4j/layout/Log4j1XmlLayoutTest.java | 11 +++--- log4j-api/pom.xml | 3 +- .../logging/log4j/message/MessageFactory.java | 7 ++-- .../log4j/message/ReusableMessageFactory.java | 3 ++ .../log4j/spi/DummyRecyclerFactory.java | 3 ++ .../log4j/spi/ThreadLocalRecyclerFactory.java | 5 +++ .../log4j/core/GarbageCollectionHelper.java | 31 +++++++++------- .../org/apache/logging/log4j/core/Layout.java | 1 - .../logging/log4j/core/async/AsyncLogger.java | 15 ++++---- .../log4j/core/impl/Log4jLogEvent.java | 1 - .../log4j/core/impl/LogEventFactory.java | 7 +--- .../core/impl/ReusableLogEventFactory.java | 2 +- .../log4j/core/layout/AbstractLayout.java | 30 ++++++---------- .../core/layout/AbstractStringLayout.java | 5 ++- .../logging/log4j/core/layout/GelfLayout.java | 12 +++---- .../layout/LockingStringBuilderEncoder.java | 11 ++---- .../log4j/core/layout/PatternLayout.java | 35 +++---------------- .../core/layout/StringBuilderEncoder.java | 7 +--- .../log4j/core/layout/TextEncoderHelper.java | 14 ++++++++ .../layout/template/json/LogstashIT.java | 2 -- .../ThreadLocalRecyclerNestedLoggingTest.java | 2 +- .../log4j/mongodb4/MongoDb4Provider.java | 16 ++++++--- .../JsonTemplateLayoutBenchmarkState.java | 4 --- .../1232_log4j-to-sfl4j-2-OSGiMetadata.xml | 29 --------------- .../.2.x.x/1366_fix_java_sql_date.xml | 28 --------------- 26 files changed, 105 insertions(+), 188 deletions(-) delete mode 100644 src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml delete mode 100644 src/changelog/.2.x.x/1366_fix_java_sql_date.xml 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 c0137f4296f..a118c758b14 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 @@ -16,19 +16,20 @@ */ package org.apache.log4j.builders.layout; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + import org.apache.log4j.Layout; import org.apache.log4j.bridge.LayoutWrapper; import org.apache.log4j.builders.AbstractBuilder; 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; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; - import static org.apache.log4j.builders.BuilderManager.NAMESPACE; import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; import static org.apache.log4j.xml.XmlConfiguration.forEachElement; @@ -73,6 +74,6 @@ public Layout parse(PropertiesConfiguration config) { } private Layout createLayout(boolean properties, 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/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java index 1c2cf7130b6..7b253e42f15 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 @@ -16,17 +16,18 @@ */ package org.apache.log4j.layout; -import static org.junit.Assert.assertEquals; - 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.test.junit.ThreadContextRule; import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.ThreadContextRule; import org.apache.logging.log4j.util.StringMap; import org.junit.Rule; import org.junit.Test; +import static org.junit.Assert.assertEquals; + public class Log4j1XmlLayoutTest { @Rule @@ -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/pom.xml b/log4j-api/pom.xml index a1b7809b330..e009643aa25 100644 --- a/log4j-api/pom.xml +++ b/log4j-api/pom.xml @@ -57,7 +57,8 @@ org.apache.logging.log4j.* - sun.reflect;resolution:=optional,* + sun.reflect;resolution:=optional, + * org.apache.logging.log4j.util.Activator <_fixupmessages>"Classes found in the wrong directory";is:=warning diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java index fa777d31104..0ed0cc70ef9 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 @@ -247,9 +247,6 @@ default Message newMessage(final String message, final Object p0, final Object p * @since 3.0.0 * @see Recycler */ - default void recycle(Message message) { - if (message instanceof ReusableMessage) { - ((ReusableMessage) message).clear(); - } - } + default void recycle(Message message) {} + } 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 7410005c0a4..b435ed98f90 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 @@ -82,6 +82,9 @@ 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); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index 5f6f6d60192..ebe8154e9cc 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -19,6 +19,8 @@ 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. * @@ -36,6 +38,7 @@ public static DummyRecyclerFactory getInstance() { @Override public Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); return new DummyRecycler<>(supplier); } 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 index 43144a5ae8c..629cb580e99 100644 --- 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 @@ -22,6 +22,8 @@ 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}. *

@@ -50,6 +52,8 @@ public static ThreadLocalRecyclerFactory getInstance() { @Override public Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); + requireNonNull(cleaner, "cleaner"); return new ThreadLocalRecycler<>(supplier, cleaner); } @@ -77,6 +81,7 @@ public V acquire() { @Override public void release(final V value) { + requireNonNull(value, "value"); cleaner.accept(value); holder.get().offer(value); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java index 8ed89f3f697..afb38179f50 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core; +import com.google.common.io.ByteStreams; + import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; @@ -26,23 +28,26 @@ import static org.junit.Assert.assertTrue; public final class GarbageCollectionHelper implements Closeable, Runnable { - private static final OutputStream sink = OutputStream.nullOutputStream(); + private static final OutputStream sink = ByteStreams.nullOutputStream(); private final AtomicBoolean running = new AtomicBoolean(); private final CountDownLatch latch = new CountDownLatch(1); - private final Thread gcThread = new Thread(() -> { - try { - while (running.get()) { - // Allocate data to help suggest a GC - try { - // 1mb of heap - sink.write(new byte[1024 * 1024]); - } catch (IOException ignored) { + private final Thread gcThread = new Thread(new Runnable() { + @Override + public void run() { + try { + while (running.get()) { + // Allocate data to help suggest a GC + try { + // 1mb of heap + sink.write(new byte[1024 * 1024]); + } catch (IOException ignored) { + } + // May no-op depending on the jvm configuration + System.gc(); } - // May no-op depending on the jvm configuration - System.gc(); + } finally { + latch.countDown(); } - } finally { - latch.countDown(); } }); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java index b833b060de4..c2308bdb1ad 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java @@ -97,5 +97,4 @@ default Charset getCharset() { default boolean requiresLocation() { return false; } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java index 1796931add0..e314e6cf9c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java @@ -37,7 +37,6 @@ import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringMap; @@ -419,7 +418,7 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin event.setValues(asyncLogger, asyncLogger.getName(), marker, fqcn, level, message, thrown, // config properties are taken care of in the EventHandler thread // in the AsyncLogger#actualAsyncLog method - contextDataInjector.injectContextData(null, event.getContextData()), + contextDataInjector.injectContextData(null, (StringMap) event.getContextData()), contextStack, currentThread.getId(), threadName, currentThread.getPriority(), location, clock, nanoClock); } @@ -493,24 +492,24 @@ public void actualAsyncLog(final RingBufferLogEvent event) { privateConfigLoggerConfig.getReliabilityStrategy().log(this, event); } - @PerformanceSensitive("allocation") + @SuppressWarnings("ForLoopReplaceableByForEach") // Avoid iterator allocation private void onPropertiesPresent(final RingBufferLogEvent event, final List properties) { final StringMap contextData = getContextData(event); - // List::forEach is garbage-free when using an ArrayList or Arrays.asList - properties.forEach(prop -> { + for (int i = 0, size = properties.size(); i < size; i++) { + final Property prop = properties.get(i); if (contextData.getValue(prop.getName()) != null) { - return; // contextMap overrides config properties + continue; // contextMap overrides config properties } final String value = prop.isValueNeedsLookup() // ? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) // : prop.getValue(); contextData.putValue(prop.getName(), value); - }); + } event.setContextData(contextData); } private static StringMap getContextData(final RingBufferLogEvent event) { - final StringMap contextData = event.getContextData(); + final StringMap contextData = (StringMap) event.getContextData(); if (contextData.isFrozen()) { final StringMap temp = ContextDataFactory.createContextData(); temp.putAll(contextData); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java index 206a8924c59..ad3f4d57b5d 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 @@ -24,7 +24,6 @@ import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.ReusableLogEvent; -import org.apache.logging.log4j.core.async.RingBufferLogEvent; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.ClockFactory; 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 86a29166b5c..c1b3706e4da 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 @@ -21,7 +21,6 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.ReusableLogEvent; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.plugins.di.Key; @@ -47,9 +46,5 @@ default LogEvent createEvent( return createEvent(loggerName, marker, fqcn, level, data, properties, t); } - default void recycle(final LogEvent event) { - if (event instanceof ReusableLogEvent) { - ((ReusableLogEvent) event).clear(); - } - } + default void recycle(final LogEvent event) {} } 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 c4990416f50..6f7f12f6085 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 @@ -34,7 +34,7 @@ import org.apache.logging.log4j.spi.RecyclerFactory; /** - * Garbage-free LogEventFactory that recycles mutable LogEvent instances. + * Garbage-free LogEventFactory that recycles mutable {@link LogEvent} instances. * @since 2.6 * @see Recycler */ 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 34a595e33b3..9af3e192dfd 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 @@ -129,22 +129,6 @@ public B setHeader(final byte[] header) { */ protected final byte[] header; - /** - * Constructs a UTF-8 encoded layout with an optional header and footer. - * - * @param configuration a configuration - * @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 - * @deprecated use {@link AbstractLayout#AbstractLayout(Configuration, Charset, byte[], byte[])} instead - */ - @Deprecated - public AbstractLayout( - final Configuration configuration, - final byte[] header, - final byte[] footer) { - this(configuration, DEFAULT_CHARSET, header, footer); - } - /** * Constructs a layout with an optional header and footer. * @@ -213,17 +197,23 @@ 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: *

- *
{@code @Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true)
+     * 
{@code
+     * @Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true)
      * @Plugin("MyLayout")
      * public final class MyLayout extends AbstractStringLayout {
      *     @Override
      *     public void encode(LogEvent event, ByteBufferDestination destination) {
-     *         StringBuilder text = acquireStringBuilder();
+     *         StringBuilder text = stringBuilderRecycler.acquire();
      *         try {
      *             convertLogEventToText(event, text);
-     *             getStringBuilderEncoder().encode(text, destination);
+     *             StringBuilderEncoder encoder = stringBuilderEncoderRecycler.acquire();
+     *             try {
+     *                 encoder.encode(text, destination);
+     *             } finally {
+     *                 stringBuilderEncoderRecycler.release(encoder);
+     *             }
      *         } finally {
-     *             releaseStringBuilder(text);
+     *             stringBuilderRecycler.release(text);
      *         }
      *     }
      *
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 dee942e936e..20f9e2e9a5a 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
@@ -113,8 +113,7 @@ protected static Recycler createStringBuilderRecycler(final Recyc
                 stringBuilder -> {
                     StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE);
                     stringBuilder.setLength(0);
-                }
-        );
+                });
     }
 
     private final Serializer footerSerializer;
@@ -150,7 +149,7 @@ protected AbstractStringLayout(
 
     /**
      * Builds a new layout.
-     * @param configuration the configuration
+     * @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
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 eb43cd551ef..1ab7276cdce 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
@@ -176,20 +176,19 @@ public GelfLayout build() {
                         + "ignoring message pattern");
                 messagePattern = null;
             }
-            final Configuration config = getConfiguration();
             if (messagePattern != null) {
                 patternLayout = PatternLayout.newBuilder().setPattern(messagePattern)
                         .setAlwaysWriteExceptions(includeStacktrace)
-                        .setConfiguration(config)
+                        .setConfiguration(getConfiguration())
                         .build();
             }
             if (patternSelector != null) {
                 patternLayout = PatternLayout.newBuilder().setPatternSelector(patternSelector)
                         .setAlwaysWriteExceptions(includeStacktrace)
-                        .setConfiguration(config)
+                        .setConfiguration(getConfiguration())
                         .build();
             }
-            return new GelfLayout(config, host, additionalFields, compressionType, compressionThreshold,
+            return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold,
                     includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter,
                     includeNewLineDelimiter, omitEmptyFields, mdcChecker, mapChecker, patternLayout,
                     threadContextPrefix, mapPrefix);
@@ -467,8 +466,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue
                     final StringBuilder stringBuilder = writer.getBuilder();
                     StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE);
                     stringBuilder.setLength(0);
-                }
-        );
+                });
     }
 
     @Override
@@ -490,7 +488,7 @@ public String toString() {
             sb.append(", ").append(mapVars);
         }
         if (layout != null) {
-            sb.append(", PatternLayout{").append(layout).append("}");
+            sb.append(", PatternLayout{").append(layout.toString()).append("}");
         }
         return sb.toString();
     }
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 666ad84d910..a64b2d3d824 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
@@ -16,22 +16,19 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.status.StatusLogger;
-
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CodingErrorAction;
 import java.util.Objects;
 
+import org.apache.logging.log4j.core.util.Constants;
+
 /**
  * Encoder for StringBuilders that locks on the ByteBufferDestination.
  */
 public class LockingStringBuilderEncoder implements Encoder {
 
-    private static final StatusLogger LOGGER = StatusLogger.getLogger();
-
     private final Charset charset;
     private final CharsetEncoder charsetEncoder;
     private final CharBuffer cachedCharBuffer;
@@ -60,9 +57,7 @@ public void encode(final StringBuilder source, final ByteBufferDestination desti
                     destination);
             }
         } catch (final Exception error) {
-            LOGGER.error("Due to `TextEncoderHelper.encodeText()` failure, falling back to `String#getBytes(Charset)`", error);
-            byte[] sourceBytes = source.toString().getBytes(charset);
-            destination.writeBytes(sourceBytes, 0, sourceBytes.length);
+            TextEncoderHelper.encodeTextFallback(charset, source, destination, error);
         }
 
     }
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 a1840c8b018..50f473f1fd8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -38,9 +38,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.LoggingSystem;
 import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.PropertyEnvironment;
 import org.apache.logging.log4j.util.Strings;
@@ -106,17 +104,9 @@ public final class PatternLayout extends AbstractStringLayout {
      * @param headerPattern header conversion pattern.
      * @param footerPattern footer conversion pattern.
      */
-    private PatternLayout(
-            final Configuration config,
-            final RecyclerFactory recyclerFactory,
-            final RegexReplacement replace,
-            final String eventPattern,
-            final PatternSelector patternSelector,
-            final Charset charset,
-            final boolean alwaysWriteExceptions,
-            final boolean disableAnsi,
-            final boolean noConsoleNoAnsi,
-            final String headerPattern,
+    private PatternLayout(final Configuration config, final RegexReplacement replace, final String eventPattern,
+            final PatternSelector patternSelector, final Charset charset, final boolean alwaysWriteExceptions,
+            final boolean disableAnsi, final boolean noConsoleNoAnsi, final String headerPattern,
             final String footerPattern) {
         super(config, charset,
                 newSerializerBuilder()
@@ -414,9 +404,7 @@ public Serializer build() {
                     PatternSerializer serializer = hasFormattingInfo
                             ? new PatternFormatterPatternSerializer(formatters, recycler)
                             : new NoFormatPatternSerializer(formatters, recycler);
-                    return replace == null
-                            ? serializer
-                            : new PatternSerializerWithReplacement(serializer, replace, recycler);
+                    return replace == null ? serializer : new PatternSerializerWithReplacement(serializer, replace, recycler);
                 } catch (final RuntimeException ex) {
                     throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex);
                 }
@@ -570,9 +558,6 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui
         @PluginConfiguration
         private Configuration configuration;
 
-        @PluginBuilderAttribute
-        private RecyclerFactory recyclerFactory;
-
         @PluginElement("Replace")
         private RegexReplacement regexReplacement;
 
@@ -596,7 +581,6 @@ public static class Builder implements org.apache.logging.log4j.plugins.util.Bui
         private String footer;
 
         private Builder() {
-            setCharset(Charset.defaultCharset());   // LOG4J2-783 Default should not be UTF-8
         }
 
         private boolean useAnsiEscapeCodes() {
@@ -633,14 +617,6 @@ public Builder setConfiguration(final Configuration configuration) {
             return this;
         }
 
-        /**
-         * @param recyclerFactory a recycler factory
-         */
-        public Builder setRecyclerFactory(final RecyclerFactory recyclerFactory) {
-            this.recyclerFactory = recyclerFactory;
-            return this;
-        }
-
         /**
          * @param regexReplacement
          *        A Regex replacement
@@ -714,8 +690,7 @@ public PatternLayout build() {
             if (configuration == null) {
                 configuration = new DefaultConfiguration();
             }
-            return new PatternLayout(
-                    configuration, recyclerFactory, regexReplacement, pattern, patternSelector, charset,
+            return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset,
                 alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, header, footer);
         }
     }
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 6ddf39112c7..308b8584fcf 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
@@ -25,7 +25,6 @@
 
 import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.spi.RecyclerFactory;
-import org.apache.logging.log4j.status.StatusLogger;
 
 /**
  * {@link Encoder} for {@link StringBuilder}s.
@@ -36,8 +35,6 @@
  */
 public class StringBuilderEncoder implements Encoder {
 
-    private static final StatusLogger LOGGER = StatusLogger.getLogger();
-
     private final CharsetEncoder charsetEncoder;
 
     private final CharBuffer charBuffer;
@@ -75,9 +72,7 @@ public void encode(final StringBuilder source, final ByteBufferDestination desti
         try {
             TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination);
         } catch (final Exception error) {
-            LOGGER.error("Due to `TextEncoderHelper.encodeText()` failure, falling back to `String#getBytes(Charset)`", error);
-            byte[] sourceBytes = source.toString().getBytes(charset);
-            destination.writeBytes(sourceBytes, 0, sourceBytes.length);
+            TextEncoderHelper.encodeTextFallback(charset, source, destination, error);
         } finally {
             charsetEncoder.reset();
             charBuffer.clear();
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 2b5d643fb97..77d334362ad 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,6 +35,18 @@ public class TextEncoderHelper {
     private TextEncoderHelper() {
     }
 
+    /* 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);
+    }
+
     /**
      * Converts the specified text to bytes and writes the resulting bytes to the specified destination.
      * Attempts to postpone synchronizing on the destination as long as possible to minimize lock contention.
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 7363a0b9a1f..f67690ed6b1 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.util.NetUtils;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.apache.logging.log4j.spi.ThreadLocalRecyclerFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.assertj.core.api.Assertions;
 import org.awaitility.Awaitility;
@@ -124,7 +123,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 639fc0887f4..8d21b1d6cc2 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
@@ -31,7 +31,7 @@
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests if logging while trying to encode an event causes {@link ThreadLocalRecyclerFactory} to incorrectly share buffers and end up overriding layout's earlier encoding work.
+ * 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-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
index 21dfe30eabc..3268b874deb 100644
--- a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
@@ -16,11 +16,6 @@
  */
 package org.apache.logging.log4j.mongodb4;
 
-import com.mongodb.ConnectionString;
-import com.mongodb.MongoClientSettings;
-import com.mongodb.client.MongoClient;
-import com.mongodb.client.MongoClients;
-import com.mongodb.client.MongoDatabase;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
@@ -33,6 +28,12 @@
 import org.bson.codecs.configuration.CodecRegistries;
 import org.bson.codecs.configuration.CodecRegistry;
 
+import com.mongodb.ConnectionString;
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoDatabase;
+
 /**
  * The MongoDB implementation of {@link NoSqlProvider} using the MongoDB driver
  * version 4 API.
@@ -59,6 +60,11 @@ public MongoDb4Provider build() {
             return new MongoDb4Provider(connection, capped, collectionSize);
         }
 
+        public B setConnectionStringSource(final String connection) {
+            this.connection = connection;
+            return asBuilder();
+        }
+
         public B setCapped(final boolean isCapped) {
             this.capped = isCapped;
             return asBuilder();
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 4a8c21517fc..616c9a1353f 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
@@ -30,7 +30,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.spi.ThreadLocalRecyclerFactory;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 
@@ -86,7 +85,6 @@ private static JsonTemplateLayout createJtl4JsonLayout() {
                 .setConfiguration(CONFIGURATION)
                 .setCharset(CHARSET)
                 .setEventTemplateUri("classpath:JsonLayout.json")
-                .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
                 .build();
     }
 
@@ -104,7 +102,6 @@ private static JsonTemplateLayout createJtl4EcsLayout() {
                 .setConfiguration(CONFIGURATION)
                 .setCharset(CHARSET)
                 .setEventTemplateUri("classpath:EcsLayout.json")
-                .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
                 .setEventTemplateAdditionalFields(additionalFields)
                 .build();
     }
@@ -115,7 +112,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/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml b/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml
deleted file mode 100644
index b166412c830..00000000000
--- a/src/changelog/.2.x.x/1232_log4j-to-sfl4j-2-OSGiMetadata.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-  
-  
-  
-  
-    Adapt the OSGi metadata of log4j-to-slf4j to work with slf4j 1 and 2.
-    To achieve that use a version range of `[1.7,3)` for the imported slf4j packages.	
-  
-
diff --git a/src/changelog/.2.x.x/1366_fix_java_sql_date.xml b/src/changelog/.2.x.x/1366_fix_java_sql_date.xml
deleted file mode 100644
index 8a28a5bd2e4..00000000000
--- a/src/changelog/.2.x.x/1366_fix_java_sql_date.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-  
-  
-  
-  
-    Fixed logging of java.sql.Date objects by appending it before Log4J tries to call java.util.Date.toInstant() on it.
-  
-

From 30055745a6224c73f50991ceece9b320e12d63bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= 
Date: Wed, 29 Mar 2023 13:10:17 +0200
Subject: [PATCH 29/39] Fix tests

---
 .../appender/SyslogAppenderCustomLayoutTest.java |  4 ++--
 .../log4j/core/appender/SyslogAppenderTest.java  |  2 ++
 .../core/appender/TlsSyslogAppenderTest.java     |  3 ++-
 ...ingAppenderDirectWriteWithHtmlLayoutTest.java | 16 +++++++++-------
 .../log4j/core/layout/GelfLayoutTest.java        |  2 ++
 .../log4j/core/layout/Rfc5424LayoutTest.java     |  6 +++++-
 .../log4j/core/layout/SyslogLayoutTest.java      |  2 ++
 7 files changed, 24 insertions(+), 11 deletions(-)

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 68b2f005a05..4a57bf787f8 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
@@ -14,10 +14,10 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.log4j.core.appender;
 
 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;
 
@@ -31,7 +31,7 @@ protected Facility getExpectedFacility() {
     @Override
     protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) {
         final Builder builder = super.newSyslogAppenderBuilder(protocol, format, newLine);
-        builder.setLayout(SyslogLayout.newBuilder().setFacility(Facility.LOCAL3).setIncludeNewLine(true).build());
+        builder.setLayout(SyslogLayout.newBuilder().setConfiguration(new DefaultConfiguration()).setFacility(Facility.LOCAL3).setIncludeNewLine(true).build());
         return builder;
     }
 
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 ad0a0dd3e6f..a34d8d3622d 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.Protocol;
 import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory;
 import org.apache.logging.log4j.util.EnglishEnums;
@@ -116,6 +117,7 @@ protected SyslogAppender createAppender(final String protocol, final String form
     protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) {
         // @formatter:off
         return SyslogAppender.newSyslogAppenderBuilder()
+                .setConfiguration(new DefaultConfiguration())
                 .setPort(PORTNUM)
                 .setProtocol(EnglishEnums.valueOf(Protocol.class, protocol))
                 .setReconnectDelayMillis(-1)
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
index 756d21d9b2d..06b33c73e41 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
@@ -22,6 +22,7 @@
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
 
+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.net.ssl.KeyStoreConfiguration;
@@ -110,7 +111,7 @@ private SyslogAppender createAppender() {
                 .setName("TestApp")
                 .setImmediateFlush(true)
                 .setIgnoreExceptions(false).setFilter(null)
-                .setConfiguration(null)
+                .setConfiguration(new DefaultConfiguration())
                 .setAdvertise(false)
                 .setFacility(Facility.LOCAL0)
                 .setId("Audit")
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java
index 786753f1397..286729f1d1a 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java
@@ -16,8 +16,16 @@
  */
 package org.apache.logging.log4j.core.appender.rolling;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import org.apache.logging.log4j.core.appender.RollingFileAppender;
 import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.layout.HtmlLayout;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
@@ -28,13 +36,6 @@
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that;
 import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -65,6 +66,7 @@ public void testRollingFileAppenderWithHtmlLayoutNoAppend(final Configuration co
     private void checkAppenderWithHtmlLayout(boolean append, final Configuration config) throws InterruptedException, IOException {
         String prefix = "testHtml_" + (append ? "append_" : "noAppend_");
         RollingFileAppender appender = RollingFileAppender.newBuilder()
+                .setConfiguration(new DefaultConfiguration())
                 .setName("RollingHtml")
                 .setFilePattern(DIR + "/" + prefix + "_-%d{MM-dd-yy-HH-mm}-%i.html")
                 .setPolicy(new SizeBasedTriggeringPolicy(500))
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 0b63af7f98a..11949f3cd88 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.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
+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;
@@ -299,6 +300,7 @@ public void testFormatTimestamp() {
     private void testRequiresLocation(String messagePattern, Boolean requiresLocation) {
 
         GelfLayout layout = GelfLayout.newBuilder()
+                .setConfiguration(new DefaultConfiguration())
                 .setMessagePattern(messagePattern)
                 .build();
 
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 d053b92e51d..4b5d69dbe68 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
@@ -31,6 +31,7 @@
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
+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;
@@ -538,6 +539,7 @@ void testLayoutBuilder() {
         }
 
         final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder()
+                .setConfig(new DefaultConfiguration())
                 .setFacility(Facility.LOCAL0)
                 .setId("Event")
                 .setEin("1234.56.7")
@@ -569,7 +571,7 @@ void testLayoutBuilder() {
 
     @Test
     public void testLayoutBuilderDefaultValues() {
-        final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().build();
+        final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().setConfig(new DefaultConfiguration()).build();
         checkDefaultValues(layout);
 
         final PluginNamespace corePlugins = ctx.getInjector().getInstance(Core.PLUGIN_NAMESPACE_KEY);
@@ -599,6 +601,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(String eid) {
         final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder()
+                .setConfig(new DefaultConfiguration())
                 .setEin(eid)
                 .build();
 
@@ -609,6 +612,7 @@ void testLayoutBuilderValidEids(String eid) {
     @ValueSource(strings = { "abc", "someEid", "-1" })
     void testLayoutBuilderInvalidEids(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/SyslogLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java
index a75753ebea4..8955b48a75e 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
@@ -26,6 +26,7 @@
 import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
+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;
@@ -75,6 +76,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();

From 13b507b790d396548094386d54e7c21308efa9bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= 
Date: Wed, 29 Mar 2023 13:55:10 +0200
Subject: [PATCH 30/39] Remove `ThreadLocal`s in `Strings`

---
 .../logging/log4j/message/MapMessageTest.java | 16 ++----
 .../logging/log4j/util/StringsTest.java       | 14 +----
 .../apache/logging/log4j/util/Strings.java    | 45 ---------------
 .../logging/log4j/core/layout/GelfLayout.java | 18 ++++--
 .../core/pattern/RepeatPatternConverter.java  |  2 +-
 .../template/json/JsonTemplateLayoutTest.java | 35 +++---------
 .../template/json/util/JsonWriterTest.java    | 55 ++++++++-----------
 .../JsonTemplateLayoutBenchmarkReport.java    |  8 +--
 8 files changed, 55 insertions(+), 138 deletions(-)

diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java
index b4e67f3bc22..1b2bc8336ee 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java
@@ -19,17 +19,13 @@
 import java.math.BigDecimal;
 import java.sql.Date;
 import java.sql.Time;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
 
 import org.apache.logging.log4j.util.StringBuilderFormattable;
-import org.apache.logging.log4j.util.Strings;
 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.assertThrows;
 
 /**
  *
@@ -175,11 +171,7 @@ public void testJsonFormatterMaxDepthViolation() {
     @Test
     public void testJsonFormatterMaxDepthConformance() {
         int depth = MapMessageJsonFormatter.MAX_DEPTH - 2;
-        String expectedJson = String
-                .format("{'key':%s1%s}",
-                        Strings.repeat("[", depth),
-                        Strings.repeat("]", depth))
-                .replace('\'', '"');
+        String expectedJson = String.format("{'key':%s1%s}", "[".repeat(depth), "]".repeat(depth)).replace('\'', '"');
         String actualJson = testJsonFormatterMaxDepth(depth);
         assertEquals(expectedJson, actualJson);
     }
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 7c9695d3a81..e2c2f2b236b 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
@@ -14,15 +14,14 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-
 package org.apache.logging.log4j.util;
 
-import org.junit.jupiter.api.Test;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 
+import org.junit.jupiter.api.Test;
+
 import static org.junit.jupiter.api.Assertions.*;
 
 public class StringsTest {
@@ -55,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/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
index 8a6fdfefb08..e88ac33fbd5 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.
      */
@@ -313,47 +311,4 @@ public static String[] splitList(String string) {
         return string != null ? string.split(COMMA_DELIMITED_RE) : new String[0];
     }
 
-    /**
-     * 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/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 1ab7276cdce..e61206e562b 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
@@ -102,7 +102,7 @@ public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) th
     private final PatternLayout layout;
     private final FieldWriter mdcWriter;
     private final FieldWriter mapWriter;
-    private final Recycler stacktraceRecycler;
+    private final Recycler stringBuilderWriterRecycler;
 
     public static class Builder> extends AbstractStringLayout.Builder
         implements org.apache.logging.log4j.plugins.util.Builder {
@@ -460,7 +460,7 @@ private GelfLayout(final Configuration config, final String host, final KeyValue
         this.mdcWriter = new FieldWriter(mdcChecker, mdcPrefix);
         this.mapWriter = new FieldWriter(mapChecker, mapPrefix);
         this.layout = patternLayout;
-        stacktraceRecycler = config.getRecyclerFactory().create(
+        stringBuilderWriterRecycler = config.getRecyclerFactory().create(
                 () -> new StringBuilderWriter(MAX_STRING_BUILDER_SIZE),
                 writer -> {
                     final StringBuilder stringBuilder = writer.getBuilder();
@@ -626,12 +626,12 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder,
                 }
             } else {
                 if (includeStacktrace) {
-                    final StringBuilderWriter writer = stacktraceRecycler.acquire();
+                    final StringBuilderWriter writer = stringBuilderWriterRecycler.acquire();
                     try {
                         formatThrowableTo(writer, event.getThrown());
                         JsonUtils.quoteAsString(writer.getBuilder(), builder);
                     } finally {
-                        stacktraceRecycler.release(writer);
+                        stringBuilderWriterRecycler.release(writer);
                     }
                 } else {
                     JsonUtils.quoteAsString(event.getThrown().toString(), builder);
@@ -684,7 +684,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);
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 59d6a8d8f20..744c17199e6 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
@@ -58,7 +58,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-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 a1a53a4d4d3..cba309490de 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;
@@ -56,24 +47,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 com.fasterxml.jackson.databind.JsonNode;
@@ -721,7 +700,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
@@ -734,8 +713,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/util/JsonWriterTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
index 5ce6e1f1d48..967ccbd77bc 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
@@ -16,32 +16,25 @@
  */
 package org.apache.logging.log4j.layout.template.json.util;
 
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
 import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
 import org.apache.logging.log4j.layout.template.json.JacksonFixture;
 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
 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;
 
-import java.io.IOException;
-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.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
 @SuppressWarnings("DoubleBraceInitialization")
 class JsonWriterTest {
 
@@ -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-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 c9715706462..771ebd2b87b 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
@@ -16,9 +16,6 @@
  */
 package org.apache.logging.log4j.layout.template.json;
 
-import org.apache.logging.log4j.core.util.JsonReader;
-import org.apache.logging.log4j.util.Strings;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -40,6 +37,9 @@
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import org.apache.logging.log4j.core.util.JsonReader;
+import org.apache.logging.log4j.util.Strings;
+
 /**
  * Utility class to summarize {@link JsonTemplateLayoutBenchmark} results in Asciidoctor.
  * 

@@ -361,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() From a9114d8a9521512401ef2f857c83063144585529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 29 Mar 2023 14:04:03 +0200 Subject: [PATCH 31/39] Remove `ThreadLocal`s in `JsonUtils` --- .../logging/log4j/core/util/JsonUtils.java | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) 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 fede436bb2c..059b963ad04 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); } } From 6faaf2025e9eb036ff561aa2f21e37f7678d89aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 29 Mar 2023 14:52:45 +0200 Subject: [PATCH 32/39] Remove `ThreadLocal`s in `ParametrizedMessage` --- .../log4j/message/ParameterizedMessage.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) 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 811d80f0e40..74879b89c89 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; @@ -67,8 +68,12 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable { private static final int HASHVAL = 31; - // 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; @@ -183,24 +188,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) { From cefdb05c6d8967a17b3146d3d0b31e93c5b62f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 29 Mar 2023 15:12:28 +0200 Subject: [PATCH 33/39] Fix minor issues --- .../log4j/core/GarbageCollectionHelper.java | 4 ++-- ...sonTemplateLayoutConcurrentEncodeTest.java | 23 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java index afb38179f50..9917af6de6f 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core; -import com.google.common.io.ByteStreams; - import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; @@ -25,6 +23,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import com.google.common.io.ByteStreams; + import static org.junit.Assert.assertTrue; public final class GarbageCollectionHelper implements Closeable, Runnable { 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 5a527854652..17cdd2ec1ec 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 @@ -16,6 +16,16 @@ */ package org.apache.logging.log4j.layout.template.json; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -25,22 +35,11 @@ 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; import org.junit.jupiter.params.provider.ValueSource; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - import static org.apache.logging.log4j.layout.template.json.TestHelpers.asMap; import static org.apache.logging.log4j.layout.template.json.TestHelpers.writeJson; import static org.assertj.core.api.Assertions.assertThat; @@ -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); From 777d76cc2847b7909fdaa48a951ee775d8aaa0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 29 Mar 2023 16:13:19 +0200 Subject: [PATCH 34/39] Test fixes --- .../log4j/core/layout/HtmlLayoutTest.java | 13 ++++++++----- .../log4j/core/layout/Rfc5424LayoutTest.java | 18 +++++++++--------- .../log4j/core/appender/SyslogAppender.java | 3 +++ .../logging/log4j/core/layout/HtmlLayout.java | 3 ++- 4 files changed, 22 insertions(+), 15 deletions(-) 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 e966993c9c5..b320578bde3 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 @@ -35,6 +35,7 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationFactory; +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.time.Instant; @@ -111,6 +112,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()); @@ -144,6 +146,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); @@ -197,7 +200,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(); MyLogEvent event = new MyLogEvent(); String actual = getDateLine(layout.toSerializable(event)); @@ -208,7 +211,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(); MyLogEvent event = new MyLogEvent(); String actual = getDateLine(layout.toSerializable(event)); @@ -219,7 +222,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(); MyLogEvent event = new MyLogEvent(); String actual = getDateLine(layout.toSerializable(event)); @@ -229,7 +232,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(); MyLogEvent event = new MyLogEvent(); String actual = getDateLine(layout.toSerializable(event)); @@ -251,7 +254,7 @@ private String getDateLine(String logEventString) { } private void testLayoutWithDatePatternFixedFormat(FixedFormat format, 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(); LogEvent event = new MyLogEvent(); 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 4b5d69dbe68..81031fe0c8d 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 @@ -107,7 +107,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(); @@ -183,7 +183,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(); @@ -254,7 +254,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(); @@ -316,7 +316,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(); @@ -360,7 +360,7 @@ 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); + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, loggerFields, new DefaultConfiguration()); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -406,7 +406,7 @@ public void testLoggerFields() { }; 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); + 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(); @@ -448,7 +448,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(); @@ -483,7 +483,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(); @@ -511,7 +511,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(); 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 cb0063ecdfa..64e6ad90377 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/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java index 3401ec89859..7a69e843ca9 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,6 +32,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.LoggerConfig; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.pattern.DatePatternConverter; @@ -365,7 +366,7 @@ public byte[] getFooter() { * @return an HTML Layout. */ public static HtmlLayout createDefaultLayout() { - return newBuilder().build(); + return newBuilder().setConfiguration(new DefaultConfiguration()).build(); } @PluginFactory From 2b12ba237ad7ef950afe270464b901fa90a1e4f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 29 Mar 2023 16:42:29 +0200 Subject: [PATCH 35/39] More test fixes --- .../builders/appender/SyslogAppenderBuilder.java | 2 ++ .../log4j/builders/layout/HtmlLayoutBuilder.java | 10 ++++++---- .../apache/log4j/layout/Log4j1SyslogLayoutTest.java | 12 ++++++++---- 3 files changed, 16 insertions(+), 8 deletions(-) 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 10c0ab22277..08a516f8912 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 @@ -34,6 +34,7 @@ import org.apache.log4j.xml.XmlConfiguration; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.appender.SyslogAppender; +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.plugins.Namespace; @@ -143,6 +144,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 1c5f1f336a2..3b82641c7d2 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 @@ -16,20 +16,21 @@ */ package org.apache.log4j.builders.layout; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + import org.apache.log4j.Layout; import org.apache.log4j.bridge.LayoutWrapper; 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; import org.w3c.dom.Element; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import static org.apache.log4j.builders.BuilderManager.NAMESPACE; import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; import static org.apache.log4j.xml.XmlConfiguration.forEachElement; @@ -78,6 +79,7 @@ public Layout parse(PropertiesConfiguration config) { private Layout createLayout(String title, boolean locationInfo) { return LayoutWrapper.adapt(HtmlLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) .setTitle(title) .setLocationInfo(locationInfo) .build()); 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 2a1e0b997a6..02939b916db 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 @@ -16,10 +16,6 @@ */ package org.apache.log4j.layout; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.time.LocalDateTime; import java.time.ZoneId; import java.util.stream.Stream; @@ -27,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; @@ -36,6 +33,10 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class Log4j1SyslogLayoutTest { private static final SimpleMessage MESSAGE = new SimpleMessage("Hello world!"); @@ -71,15 +72,18 @@ static Stream configurations() { public void testSimpleLayout(String expected, Facility facility, boolean header, 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) From c72f4c3e44b59cede15c5d594e95c6dd48d0e0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Fri, 21 Apr 2023 17:58:42 +0200 Subject: [PATCH 36/39] Make sure `LogBuilder` recycles `DefaultLogBuilder` --- .../log4j/internal/DefaultLogBuilder.java | 70 ++++++++----------- .../logging/log4j/spi/AbstractLogger.java | 36 +++++++--- 2 files changed, 57 insertions(+), 49 deletions(-) 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 2690c6ee729..08ef3701dbe 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 @@ -17,21 +17,16 @@ package org.apache.logging.log4j.internal; import java.util.Arrays; +import java.util.function.Consumer; 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.InternalApi; -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.util.*; /** * Collects data for a log event and then logs it. This class should be considered private. @@ -40,25 +35,27 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { 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 final Consumer postUsageCallback; private ExtendedLogger logger; private Level level; private Marker marker; private Throwable throwable; private StackTraceElement location; - private final long threadId; private String fqcn = FQCN; - public DefaultLogBuilder(final ExtendedLogger logger, final Level level) { + public DefaultLogBuilder( + final ExtendedLogger logger, + final Level level, + final Consumer postUsageCallback) { this.logger = logger; this.level = level; - this.threadId = Thread.currentThread().getId(); + this.postUsageCallback = postUsageCallback; } public DefaultLogBuilder() { - this(null, null); + this(null, null, null); } @Override @@ -106,7 +103,7 @@ public LogBuilder withLocation(final StackTraceElement location) { @Override public void log(final Message message) { - if (isValid() && isEnabled(message)) { + if (isEnabled(message)) { logMessage(message); } } @@ -114,7 +111,7 @@ public void log(final Message message) { @Override public Message logAndGet(Supplier messageSupplier) { Message message = null; - if (isValid() && isEnabled(message = messageSupplier.get())) { + if (isEnabled(message = messageSupplier.get())) { logMessage(message); } return message; @@ -122,21 +119,21 @@ public Message logAndGet(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)); } } @@ -144,7 +141,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)); } } @@ -156,56 +153,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)); } } @@ -213,7 +210,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)); } } @@ -221,7 +218,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)); } } @@ -229,14 +226,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); } } @@ -245,23 +242,16 @@ private void logMessage(final Message message) { try { logger.logMessage(level, marker, fqcn, location, message, throwable); } finally { - // recycle self this.level = null; this.marker = null; this.throwable = null; this.location = null; + if (postUsageCallback != null) { + postUsageCallback.accept(this); + } } } - private boolean isValid() { - 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) { return logger.isEnabled(level, marker, message, throwable); } 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 014d5967a9c..afa1b6c6c78 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 @@ -69,16 +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 final Recycler recycler = LoggingSystem.getRecyclerFactory().create(() -> new DefaultLogBuilder(this, null)); + 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); } /** @@ -87,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); } /** @@ -97,9 +94,30 @@ 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 = name != null ? name : createNameFromClass(getClass()); + this.messageFactory = messageFactory != null ? messageFactory : LoggingSystem.getMessageFactory(); + this.flowMessageFactory = flowMessageFactory != null ? flowMessageFactory : LoggingSystem.getFlowMessageFactory(); + RecyclerFactory effectiveRecyclerFactory = recyclerFactory != null ? recyclerFactory : LoggingSystem.getRecyclerFactory(); + this.recycler = effectiveRecyclerFactory.create(() -> new DefaultLogBuilder(this, null, this::recycleLogBuilder)); + } + + private static String createNameFromClass(Class clazz) { + final String canonicalName = clazz.getCanonicalName(); + return canonicalName != null ? canonicalName : clazz.getName(); + } + + private void recycleLogBuilder(DefaultLogBuilder logBuilder) { + if (recycler != null) { + recycler.release(logBuilder); + } } /** From 4c9a39affd854790a7add80ab9f8ee24eb2f9d4e Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sat, 22 Apr 2023 22:01:26 +0200 Subject: [PATCH 37/39] Add RecyclerAware interface Classes implementing this interface are aware of recyclers and can release themselves after usage. --- .../log4j/internal/DefaultLogBuilder.java | 21 +++++---- .../logging/log4j/spi/AbstractLogger.java | 2 +- .../logging/log4j/spi/AbstractRecycler.java | 45 +++++++++++++++++++ .../log4j/spi/DummyRecyclerFactory.java | 8 ++-- .../log4j/spi/QueueingRecyclerFactory.java | 13 ++---- .../logging/log4j/spi/RecyclerAware.java | 24 ++++++++++ .../log4j/spi/ThreadLocalRecyclerFactory.java | 13 ++---- 7 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java create mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java 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 08ef3701dbe..87b39e1fcee 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 @@ -26,36 +26,36 @@ 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.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. */ @InternalApi -public class DefaultLogBuilder implements BridgeAware, LogBuilder { +public class DefaultLogBuilder implements BridgeAware, LogBuilder, RecyclerAware { private static final String FQCN = DefaultLogBuilder.class.getName(); private static final Message EMPTY_MESSAGE = new SimpleMessage(Strings.EMPTY); - private final Consumer postUsageCallback; private ExtendedLogger logger; private Level level; private Marker marker; private Throwable throwable; private StackTraceElement location; private String fqcn = FQCN; + private Recycler recycler; public DefaultLogBuilder( final ExtendedLogger logger, - final Level level, - final Consumer postUsageCallback) { + final Level level) { this.logger = logger; this.level = level; - this.postUsageCallback = postUsageCallback; } public DefaultLogBuilder() { - this(null, null, null); + this(null, null); } @Override @@ -246,8 +246,8 @@ private void logMessage(final Message message) { this.marker = null; this.throwable = null; this.location = null; - if (postUsageCallback != null) { - postUsageCallback.accept(this); + if (recycler != null) { + recycler.release(this); } } } @@ -334,4 +334,9 @@ 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/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java index afa1b6c6c78..dc765db629c 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 @@ -106,7 +106,7 @@ private AbstractLogger( this.messageFactory = messageFactory != null ? messageFactory : LoggingSystem.getMessageFactory(); this.flowMessageFactory = flowMessageFactory != null ? flowMessageFactory : LoggingSystem.getFlowMessageFactory(); RecyclerFactory effectiveRecyclerFactory = recyclerFactory != null ? recyclerFactory : LoggingSystem.getRecyclerFactory(); - this.recycler = effectiveRecyclerFactory.create(() -> new DefaultLogBuilder(this, null, this::recycleLogBuilder)); + this.recycler = effectiveRecyclerFactory.create(DefaultLogBuilder::new); } private static String createNameFromClass(Class clazz) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java new file mode 100644 index 00000000000..3ab2c76cf61 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java @@ -0,0 +1,45 @@ +/* + * 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; + +public abstract class AbstractRecycler implements Recycler { + + private final Supplier supplier; + private final Consumer cleaner; + + public AbstractRecycler(final Supplier supplier, final Consumer cleaner) { + this.supplier = supplier; + this.cleaner = cleaner; + } + + protected final V createObject() { + final V obj = supplier.get(); + if (obj instanceof RecyclerAware) { + ((RecyclerAware) obj).setRecycler(this); + } + return obj; + } + + protected final void cleanObject(V obj) { + if (cleaner != null) { + cleaner.accept(obj); + } + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index ebe8154e9cc..ea4ab03ce21 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -42,17 +42,15 @@ public Recycler create(final Supplier supplier, final Consumer clea return new DummyRecycler<>(supplier); } - private static class DummyRecycler implements Recycler { - - private final Supplier supplier; + private static class DummyRecycler extends AbstractRecycler { private DummyRecycler(final Supplier supplier) { - this.supplier = supplier; + super(supplier, null); } @Override public V acquire() { - return supplier.get(); + return createObject(); } @Override 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 index e0b5a17a425..1acfb3f5962 100644 --- 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 @@ -44,11 +44,7 @@ public Recycler create(final Supplier supplier, final Consumer clea } // Visible for tests - static class QueueingRecycler implements Recycler { - - private final Supplier supplier; - - private final Consumer cleaner; + static class QueueingRecycler extends AbstractRecycler { private final Queue queue; @@ -56,8 +52,7 @@ private QueueingRecycler( final Supplier supplier, final Consumer cleaner, final Queue queue) { - this.supplier = supplier; - this.cleaner = cleaner; + super(supplier, cleaner); this.queue = queue; } @@ -69,13 +64,13 @@ Queue getQueue() { @Override public V acquire() { final V value = queue.poll(); - return value != null ? value : supplier.get(); + return value != null ? value : createObject(); } @Override public void release(final V value) { requireNonNull(value, "value"); - cleaner.accept(value); + cleanObject(value); queue.offer(value); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java new file mode 100644 index 00000000000..c760ee9ea2b --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java @@ -0,0 +1,24 @@ +/* + * 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; +/** + * 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/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java index 629cb580e99..4cde452be55 100644 --- 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 @@ -58,17 +58,12 @@ public Recycler create(final Supplier supplier, final Consumer clea } // Visible for testing - static class ThreadLocalRecycler implements Recycler { - - private final Supplier supplier; - - private final Consumer cleaner; + static class ThreadLocalRecycler extends AbstractRecycler { private final ThreadLocal> holder; private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner) { - this.supplier = supplier; - this.cleaner = cleaner; + super(supplier, cleaner); this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(MAX_QUEUE_SIZE)); } @@ -76,13 +71,13 @@ private ThreadLocalRecycler(final Supplier supplier, final Consumer cleane public V acquire() { final Queue queue = holder.get(); final V value = queue.poll(); - return value != null ? value : supplier.get(); + return value != null ? value : createObject(); } @Override public void release(final V value) { requireNonNull(value, "value"); - cleaner.accept(value); + cleanObject(value); holder.get().offer(value); } From 6c0fba83ac8966628769e3b1be1cb19fc2399115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 24 Apr 2023 21:02:09 +0200 Subject: [PATCH 38/39] Simplify `AbstractRecycler` --- .../log4j/internal/DefaultLogBuilder.java | 5 ++--- .../logging/log4j/spi/AbstractLogger.java | 21 +++++++++--------- .../logging/log4j/spi/AbstractRecycler.java | 22 +++++++------------ .../log4j/spi/DummyRecyclerFactory.java | 4 ++-- .../log4j/spi/QueueingRecyclerFactory.java | 9 +++++--- .../log4j/spi/ThreadLocalRecyclerFactory.java | 9 +++++--- 6 files changed, 35 insertions(+), 35 deletions(-) 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 87b39e1fcee..3b0c13d35e0 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 @@ -47,9 +47,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder, RecyclerAware private String fqcn = FQCN; private Recycler recycler; - public DefaultLogBuilder( - final ExtendedLogger logger, - final Level level) { + public DefaultLogBuilder(final ExtendedLogger logger, final Level level) { this.logger = logger; this.level = level; } @@ -339,4 +337,5 @@ protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Obj public void setRecycler(Recycler recycler) { this.recycler = recycler; } + } 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 dc765db629c..85d389e4777 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 @@ -102,24 +102,25 @@ private AbstractLogger( final MessageFactory messageFactory, final FlowMessageFactory flowMessageFactory, final RecyclerFactory recyclerFactory) { - this.name = name != null ? name : createNameFromClass(getClass()); + this.name = createNameFromClass(name, getClass()); this.messageFactory = messageFactory != null ? messageFactory : LoggingSystem.getMessageFactory(); - this.flowMessageFactory = flowMessageFactory != null ? flowMessageFactory : LoggingSystem.getFlowMessageFactory(); - RecyclerFactory effectiveRecyclerFactory = recyclerFactory != null ? recyclerFactory : LoggingSystem.getRecyclerFactory(); + 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(Class clazz) { + private static String createNameFromClass(String name, Class clazz) { + if (name != null) { + return name; + } final String canonicalName = clazz.getCanonicalName(); return canonicalName != null ? canonicalName : clazz.getName(); } - private void recycleLogBuilder(DefaultLogBuilder logBuilder) { - if (recycler != null) { - recycler.release(logBuilder); - } - } - /** * Checks that the message factory a logger was created with is the same as the given messageFactory. If they are * different log a warning to the {@linkplain StatusLogger}. A null MessageFactory translates to the default diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java index 3ab2c76cf61..fd5da6fc608 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java @@ -16,30 +16,24 @@ */ package org.apache.logging.log4j.spi; -import java.util.function.Consumer; import java.util.function.Supplier; public abstract class AbstractRecycler implements Recycler { private final Supplier supplier; - private final Consumer cleaner; - public AbstractRecycler(final Supplier supplier, final Consumer cleaner) { + public AbstractRecycler(final Supplier supplier) { this.supplier = supplier; - this.cleaner = cleaner; } - protected final V createObject() { - final V obj = supplier.get(); - if (obj instanceof RecyclerAware) { - ((RecyclerAware) obj).setRecycler(this); + protected final V createInstance() { + final V instance = supplier.get(); + if (instance instanceof RecyclerAware) { + @SuppressWarnings("unchecked") + RecyclerAware recyclerAware = (RecyclerAware) instance; + recyclerAware.setRecycler(this); } - return obj; + return instance; } - protected final void cleanObject(V obj) { - if (cleaner != null) { - cleaner.accept(obj); - } - } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java index ea4ab03ce21..5b24427ac78 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java @@ -45,12 +45,12 @@ public Recycler create(final Supplier supplier, final Consumer clea private static class DummyRecycler extends AbstractRecycler { private DummyRecycler(final Supplier supplier) { - super(supplier, null); + super(supplier); } @Override public V acquire() { - return createObject(); + return createInstance(); } @Override 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 index 1acfb3f5962..4a69d65f04d 100644 --- 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 @@ -46,13 +46,16 @@ public Recycler create(final Supplier supplier, final Consumer clea // 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, cleaner); + super(supplier); + this.cleaner = cleaner; this.queue = queue; } @@ -64,13 +67,13 @@ Queue getQueue() { @Override public V acquire() { final V value = queue.poll(); - return value != null ? value : createObject(); + return value != null ? value : createInstance(); } @Override public void release(final V value) { requireNonNull(value, "value"); - cleanObject(value); + cleaner.accept(value); queue.offer(value); } 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 index 4cde452be55..11b28cb21e6 100644 --- 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 @@ -60,24 +60,27 @@ public Recycler create(final Supplier supplier, final Consumer clea // Visible for testing static class ThreadLocalRecycler extends AbstractRecycler { + private final Consumer cleaner; + private final ThreadLocal> holder; private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner) { - super(supplier, cleaner); + super(supplier); this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(MAX_QUEUE_SIZE)); + this.cleaner = cleaner; } @Override public V acquire() { final Queue queue = holder.get(); final V value = queue.poll(); - return value != null ? value : createObject(); + return value != null ? value : createInstance(); } @Override public void release(final V value) { requireNonNull(value, "value"); - cleanObject(value); + cleaner.accept(value); holder.get().offer(value); } From 03927348d18cb659b57a773aee8e9e34b74b93f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Mon, 24 Apr 2023 22:16:41 +0200 Subject: [PATCH 39/39] Make queue-size in `ThreadLocalRecyclerFactory` configurable --- .../message/ReusableMessageFactoryTest.java | 2 +- .../log4j/spi/RecyclerFactoriesTest.java | 27 ++++++--- .../spi/ThreadLocalRecyclerFactoryTest.java | 10 ++-- .../logging/log4j/spi/RecyclerFactories.java | 60 +++++++++++++------ .../log4j/spi/ThreadLocalRecyclerFactory.java | 40 ++++++++----- 5 files changed, 93 insertions(+), 46 deletions(-) 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 6ac576f7a88..35674ce69a6 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 @@ -31,7 +31,7 @@ public class ReusableMessageFactoryTest { @BeforeEach void setUp() { - factory = new ReusableMessageFactory(ThreadLocalRecyclerFactory.getInstance()); + factory = new ReusableMessageFactory(new ThreadLocalRecyclerFactory(8)); } @Test 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 index 265b5cb16fa..d2534bd6d43 100644 --- 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 @@ -20,13 +20,14 @@ 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 Object actualDummyRecyclerFactory = RecyclerFactories.ofSpec("dummy"); + final RecyclerFactory actualDummyRecyclerFactory = RecyclerFactories.ofSpec("dummy"); Assertions .assertThat(actualDummyRecyclerFactory) .isSameAs(DummyRecyclerFactory.getInstance()); @@ -34,15 +35,27 @@ void DummyRecyclerFactory_should_work() { @Test void ThreadLocalRecyclerFactory_should_work() { - final Object actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal"); + final RecyclerFactory actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal"); Assertions .assertThat(actualThreadLocalRecyclerFactory) - .isSameAs(ThreadLocalRecyclerFactory.getInstance()); + .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 Object actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue"); + final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue"); Assertions .assertThat(actualQueueingRecyclerFactory) .isInstanceOf(QueueingRecyclerFactory.class); @@ -50,7 +63,7 @@ void QueueingRecyclerFactory_should_work() { @Test void QueueingRecyclerFactory_should_work_with_supplier() { - final Object recyclerFactory = RecyclerFactories.ofSpec("queue:supplier=java.util.ArrayDeque.new"); + final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec("queue:supplier=java.util.ArrayDeque.new"); Assertions .assertThat(recyclerFactory) .isInstanceOf(QueueingRecyclerFactory.class); @@ -68,7 +81,7 @@ void QueueingRecyclerFactory_should_work_with_supplier() { @Test void QueueingRecyclerFactory_should_work_with_capacity() { - final Object actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue:capacity=100"); + final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue:capacity=100"); Assertions .assertThat(actualQueueingRecyclerFactory) .isInstanceOf(QueueingRecyclerFactory.class); @@ -76,7 +89,7 @@ void QueueingRecyclerFactory_should_work_with_capacity() { @Test void QueueingRecyclerFactory_should_work_with_supplier_and_capacity() { - final Object recyclerFactory = RecyclerFactories.ofSpec( + final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec( "queue:" + "supplier=java.util.concurrent.ArrayBlockingQueue.new," + "capacity=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 index 71334fdb909..a233d8724ce 100644 --- 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 @@ -30,6 +30,8 @@ class ThreadLocalRecyclerFactoryTest { + private static final int CAPACITY = 8; + private static class RecyclableObject {} private Recycler recycler; @@ -38,12 +40,12 @@ private static class RecyclableObject {} @BeforeEach void setUp() { - recycler = ThreadLocalRecyclerFactory.getInstance().create(RecyclableObject::new); + recycler = new ThreadLocalRecyclerFactory(CAPACITY).create(RecyclableObject::new); recyclerQueue = ((ThreadLocalRecyclerFactory.ThreadLocalRecycler) recycler).getQueue(); } @ParameterizedTest - @IntRangeSource(from = 1, to = ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE, closed = true) + @IntRangeSource(from = 1, to = CAPACITY, closed = true) void nested_acquires_should_not_interfere(final int acquisitionCount) { // pool should start empty @@ -90,8 +92,8 @@ void nested_acquires_past_max_queue_size_should_discard_extra_releases() { assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects); acquiredObjects.forEach(recycler::release); - // upon return, we should only have ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE retained for future use - assertThat(recyclerQueue).hasSize(ThreadLocalRecyclerFactory.MAX_QUEUE_SIZE); + // upon return, we should only have `CAPACITY` retained for future use + assertThat(recyclerQueue).hasSize(CAPACITY); } 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 index 55510019c88..87783e7ad69 100644 --- 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 @@ -27,7 +27,8 @@ public final class RecyclerFactories { - private static final int DEFAULT_QUEUE_CAPACITY = Math.max( + // Visible for testing + static final int DEFAULT_QUEUE_CAPACITY = Math.max( 2 * Runtime.getRuntime().availableProcessors() + 1, 8); @@ -35,7 +36,7 @@ private RecyclerFactories() {} public static RecyclerFactory getDefault() { return isThreadLocalsEnabled() - ? ThreadLocalRecyclerFactory.getInstance() + ? new ThreadLocalRecyclerFactory(DEFAULT_QUEUE_CAPACITY) : new QueueingRecyclerFactory(QueueFactories.MPMC.factory(DEFAULT_QUEUE_CAPACITY)); } @@ -52,8 +53,8 @@ else if (recyclerFactorySpec.equals("dummy")) { } // Is a TLA factory requested? - else if (recyclerFactorySpec.equals("threadLocal")) { - return ThreadLocalRecyclerFactory.getInstance(); + else if (recyclerFactorySpec.startsWith("threadLocal")) { + return readThreadLocalRecyclerFactory(recyclerFactorySpec); } // Is a queueing factory requested? @@ -69,28 +70,32 @@ else if (recyclerFactorySpec.startsWith("queue")) { } + 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. + // Parse the spec final String queueFactorySpec = recyclerFactorySpec.substring( "queue".length() + (recyclerFactorySpec.startsWith("queue:") ? 1 : 0)); final Map parsedValues = StringParameterParser.parse(queueFactorySpec, Set.of("supplier", "capacity")); - // Read the capacity. - final StringParameterParser.Value capacityValue = parsedValues.get("capacity"); - final int capacity; - if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) { - capacity = DEFAULT_QUEUE_CAPACITY; - } else { - try { - capacity = Integer.parseInt(capacityValue.toString()); - } catch (final NumberFormatException error) { - throw new IllegalArgumentException( - "failed reading capacity in queueing recycler factory: " + queueFactorySpec, - error); - } - } + // Read the capacity + final int capacity = readQueueCapacity(queueFactorySpec, parsedValues); // Read the supplier path final StringParameterParser.Value supplierValue = parsedValues.get("supplier"); @@ -106,4 +111,21 @@ private static RecyclerFactory readQueueingRecyclerFactory(final String recycler } + 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/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadLocalRecyclerFactory.java index 11b28cb21e6..66501f8ebf1 100644 --- 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 @@ -35,26 +35,33 @@ */ public class ThreadLocalRecyclerFactory implements RecyclerFactory { - // This determines the maximum number of recyclable objects we may retain per thread. - // This allows us to acquire recyclable objects in recursive method calls and maintain - // minimal overhead in the scenarios where the active instance count goes far beyond this - // for a brief moment. - // Visible for testing - static final int MAX_QUEUE_SIZE = 8; - - private static final ThreadLocalRecyclerFactory INSTANCE = new ThreadLocalRecyclerFactory(); - - private ThreadLocalRecyclerFactory() {} + /** + * 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; + } - public static ThreadLocalRecyclerFactory getInstance() { - return INSTANCE; + /** + * @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); + return new ThreadLocalRecycler<>(supplier, cleaner, capacity); } // Visible for testing @@ -64,9 +71,12 @@ static class ThreadLocalRecycler extends AbstractRecycler { private final ThreadLocal> holder; - private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner) { + private ThreadLocalRecycler( + final Supplier supplier, + final Consumer cleaner, + final int capacity) { super(supplier); - this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(MAX_QUEUE_SIZE)); + this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(capacity)); this.cleaner = cleaner; }