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 6ec36d5308a..93c0f27f633 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
@@ -32,7 +32,7 @@
* @since 2.6
*/
@PerformanceSensitive("allocation")
-public final class ReusableMessageFactory implements MessageFactory {
+public final class ReusableMessageFactory implements MessageFactory2 {
/**
* Instance of {@link ReusableMessageFactory}.
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 ea074c5db1b..166fd213c8c 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
@@ -152,7 +152,7 @@ private static Throwable determineThrowable(final Object[] args, final int argCo
return null;
}
- protected ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) {
+ public ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) {
init(messagePattern, arguments == null ? 0 : arguments.length, arguments);
varargs = arguments;
return this;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
index 7ce4296ba57..855340a1973 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
@@ -34,7 +34,7 @@
* This class implements all {@link MessageFactory} methods.
*
*/
-public final class StringFormatterMessageFactory implements MessageFactory {
+public final class StringFormatterMessageFactory implements MessageFactory2 {
/**
* Instance of StringFormatterMessageFactory.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
new file mode 100644
index 00000000000..879582add95
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
@@ -0,0 +1,157 @@
+/*
+ * 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.Objects;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.MessageFactory2;
+import org.apache.logging.log4j.message.SimpleMessage;
+
+/**
+ * Adapts a legacy {@link MessageFactory} to the new {@link MessageFactory2} interface.
+ *
+ * @since 2.24.0
+ */
+public class MessageFactory2Adapter implements MessageFactory2 {
+ private final MessageFactory wrapped;
+
+ public MessageFactory2Adapter(final MessageFactory wrapped) {
+ this.wrapped = Objects.requireNonNull(wrapped);
+ }
+
+ public MessageFactory getOriginal() {
+ return wrapped;
+ }
+
+ @Override
+ public Message newMessage(final CharSequence charSequence) {
+ return new SimpleMessage(charSequence);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0) {
+ return wrapped.newMessage(message, p0);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0, final Object p1) {
+ return wrapped.newMessage(message, p0, p1);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
+ return wrapped.newMessage(message, p0, p1, p2);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
+ return wrapped.newMessage(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 wrapped.newMessage(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 wrapped.newMessage(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 wrapped.newMessage(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 wrapped.newMessage(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 wrapped.newMessage(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 wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
+ }
+
+ @Override
+ public Message newMessage(final Object message) {
+ return wrapped.newMessage(message);
+ }
+
+ @Override
+ public Message newMessage(final String message) {
+ return wrapped.newMessage(message);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object... params) {
+ return wrapped.newMessage(message, params);
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index 7fe952cbd0d..3302c65323d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -28,6 +28,7 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+import org.apache.logging.log4j.Logger;
/**
* Handles {@link ServiceLoader} lookups with better error handling.
@@ -54,6 +55,12 @@ public static Stream safeStream(final ServiceLoader serviceLoader) {
.filter(service -> classes.add(service.getClass()));
}
+ // Available in Log4j API 2.x
+ public static Stream safeStream(
+ final Class ignoredServiceType, final ServiceLoader serviceLoader, final Logger ignoredStatusLogger) {
+ return safeStream(serviceLoader);
+ }
+
private static class ServiceLoaderSpliterator extends Spliterators.AbstractSpliterator {
private final Iterator serviceIterator;
private final String serviceName;
diff --git a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
index 80578ee369a..cd430f94c05 100644
--- a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
+++ b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
@@ -34,13 +34,13 @@
import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.kit.logger.AbstractLogger;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
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.plugins.Inject;
import org.apache.logging.log4j.plugins.Named;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.StringMap;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
index 49f30589562..e4c5c95dfec 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
@@ -51,7 +51,6 @@
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.StringFormatterMessageFactory;
import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.apache.logging.log4j.spi.LoggingSystem;
import org.awaitility.Awaitility;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Tag;
@@ -65,7 +64,8 @@ public class LoggerTest {
private static void checkMessageFactory(final MessageFactory messageFactory, final Logger testLogger) {
if (messageFactory == null) {
- assertSame(LoggingSystem.getMessageFactory(), testLogger.getMessageFactory());
+ final org.apache.logging.log4j.Logger newLogger = LogManager.getLogger("checkMessageFactory");
+ assertSame(newLogger.getMessageFactory(), testLogger.getMessageFactory());
} else {
final MessageFactory actual = testLogger.getMessageFactory();
assertEquals(messageFactory, actual);
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java
deleted file mode 100644
index 5f035e62397..00000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java
+++ /dev/null
@@ -1,68 +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.core.util;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * This class is borrowed from Jackson.
- */
-public class JsonUtilsTest {
-
- @Test
- public void testQuoteCharSequenceAsString() throws Exception {
- final StringBuilder output = new StringBuilder();
- final StringBuilder builder = new StringBuilder();
- builder.append("foobar");
- JsonUtils.quoteAsString(builder, output);
- assertEquals("foobar", output.toString());
- builder.setLength(0);
- output.setLength(0);
- builder.append("\"x\"");
- JsonUtils.quoteAsString(builder, output);
- assertEquals("\\\"x\\\"", output.toString());
- }
-
- // For [JACKSON-853]
- @Test
- public void testQuoteLongCharSequenceAsString() throws Exception {
- final StringBuilder output = new StringBuilder();
- final StringBuilder input = new StringBuilder();
- final StringBuilder sb2 = new StringBuilder();
- for (int i = 0; i < 1111; ++i) {
- input.append('"');
- sb2.append("\\\"");
- }
- final String exp = sb2.toString();
- JsonUtils.quoteAsString(input, output);
- assertEquals(2 * input.length(), output.length());
- assertEquals(exp, output.toString());
- }
-
- // [JACKSON-884]
- @Test
- public void testCharSequenceWithCtrlChars() throws Exception {
- final char[] input = new char[] {0, 1, 2, 3, 4};
- final StringBuilder builder = new StringBuilder();
- builder.append(input);
- final StringBuilder output = new StringBuilder();
- JsonUtils.quoteAsString(builder, output);
- assertEquals("\\u0000\\u0001\\u0002\\u0003\\u0004", output.toString());
- }
-}
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 09a6d89482c..8964b642d9f 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
@@ -29,12 +29,12 @@
import org.apache.logging.log4j.core.config.ReliabilityStrategy;
import org.apache.logging.log4j.core.filter.CompositeFilter;
import org.apache.logging.log4j.kit.logger.AbstractLogger;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
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.plugins.Inject;
import org.apache.logging.log4j.plugins.Named;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.Strings;
import org.apache.logging.log4j.util.Supplier;
import org.jspecify.annotations.NullMarked;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 64bb5855c86..3b247be1653 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -87,6 +87,8 @@ public class LoggerContext extends AbstractLifeCycle
public static final Key KEY = Key.forClass(LoggerContext.class);
private final LoggerRegistry loggerRegistry = new LoggerRegistry<>();
+ private final MessageFactory defaultMessageFactory;
+
private final Collection> configurationStartedListeners = new ArrayList<>();
private final Collection> configurationStoppedListeners = new ArrayList<>();
private final Lazy> listeners = Lazy.relaxed(CopyOnWriteArrayList::new);
@@ -139,6 +141,7 @@ public LoggerContext(
this.configLocation = configLocation;
this.environment = instanceFactory.getInstance(PropertyEnvironment.class);
this.configurationScheduler = instanceFactory.getInstance(ConfigurationScheduler.class);
+ this.defaultMessageFactory = instanceFactory.getInstance(MessageFactory.class);
this.configuration = new DefaultConfiguration(this);
this.nullConfiguration = new NullConfiguration(this);
@@ -180,10 +183,10 @@ public LoggerContext(
* @param logger The logger to check
* @param messageFactory The message factory to check.
*/
- public static void checkMessageFactory(final ExtendedLogger logger, final MessageFactory messageFactory) {
+ private void checkMessageFactory(final ExtendedLogger logger, final MessageFactory messageFactory) {
final String name = logger.getName();
final MessageFactory loggerMessageFactory = logger.getMessageFactory();
- final MessageFactory currentMessageFactory = LoggingSystem.getMessageFactory();
+ final MessageFactory currentMessageFactory = defaultMessageFactory;
if (messageFactory != null && !loggerMessageFactory.equals(messageFactory)) {
StatusLogger.getLogger()
.warn(
@@ -536,7 +539,7 @@ public Object getExternalContext() {
*/
@Override
public Logger getLogger(final String name) {
- return getLogger(name, null);
+ return getLogger(name, defaultMessageFactory);
}
/**
@@ -562,13 +565,14 @@ public Collection getLoggers() {
*/
@Override
public Logger getLogger(final String name, final MessageFactory messageFactory) {
+ final MessageFactory actualMessageFactory = messageFactory != null ? messageFactory : defaultMessageFactory;
// Note: This is the only method where we add entries to the 'loggerRegistry' ivar.
- Logger logger = loggerRegistry.getLogger(name, messageFactory);
+ Logger logger = loggerRegistry.getLogger(name, actualMessageFactory);
if (logger != null) {
- checkMessageFactory(logger, messageFactory);
+ checkMessageFactory(logger, actualMessageFactory);
return logger;
}
- logger = newLogger(name, messageFactory);
+ logger = newLogger(name, actualMessageFactory);
loggerRegistry.putIfAbsent(name, logger.getMessageFactory(), logger);
return loggerRegistry.getLogger(name, logger.getMessageFactory());
}
@@ -602,7 +606,7 @@ public InstanceFactory getInstanceFactory() {
*/
@Override
public boolean hasLogger(final String name) {
- return loggerRegistry.hasLogger(name);
+ return loggerRegistry.hasLogger(name, defaultMessageFactory);
}
/**
@@ -906,13 +910,12 @@ protected Class extends Logger.Builder> getLoggerBuilderClass() {
return Logger.Builder.class;
}
- private Logger newLogger(final String name, final @Nullable MessageFactory messageFactory) {
- final Logger.Builder builder =
- instanceFactory.getInstance(getLoggerBuilderClass()).setName(name);
- if (messageFactory != null) {
- builder.setMessageFactory(messageFactory);
- }
- return builder.build();
+ private Logger newLogger(final String name, final MessageFactory messageFactory) {
+ return instanceFactory
+ .getInstance(getLoggerBuilderClass())
+ .setName(name)
+ .setMessageFactory(messageFactory)
+ .build();
}
/**
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 8f0a8fcb094..b00280d4f3b 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
@@ -36,9 +36,9 @@
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.core.util.WatchManager;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Interface that must be implemented to create a configuration.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
index 2a21b56b228..f9b66888fb3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
@@ -28,6 +28,8 @@
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.StructuredDataMessage;
import org.apache.logging.log4j.plugins.Configurable;
@@ -35,8 +37,6 @@
import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreDefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreDefaultBundle.java
index b2fd4d74fef..85f58a55c07 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreDefaultBundle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreDefaultBundle.java
@@ -16,8 +16,12 @@
*/
package org.apache.logging.log4j.core.impl;
+import java.util.Comparator;
import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceLoader;
import java.util.function.Supplier;
+import java.util.stream.Stream;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
@@ -29,6 +33,7 @@
import org.apache.logging.log4j.core.config.URIConfigurationFactory;
import org.apache.logging.log4j.core.config.composite.DefaultMergeStrategy;
import org.apache.logging.log4j.core.config.composite.MergeStrategy;
+import org.apache.logging.log4j.core.impl.internal.ReusableMessageFactory;
import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.InterpolatorFactory;
@@ -43,6 +48,10 @@
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerProperties;
+import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
import org.apache.logging.log4j.message.FlowMessageFactory;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.plugins.Factory;
@@ -53,11 +62,10 @@
import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory;
import org.apache.logging.log4j.spi.CopyOnWrite;
import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.LoggingSystem;
import org.apache.logging.log4j.spi.Provider;
import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
/**
* Provides instance binding defaults.
@@ -88,20 +96,36 @@ public LoggerContextFactory loggerContextFactory(final ConfigurableInstanceFacto
@SingletonFactory
@ConditionalOnMissingBinding
- public MessageFactory defaultMessageFactory() {
- return LoggingSystem.getMessageFactory();
+ public MessageFactory defaultMessageFactory(final RecyclerFactory recyclerFactory) {
+ return new ReusableMessageFactory(recyclerFactory);
}
@SingletonFactory
@ConditionalOnMissingBinding
public FlowMessageFactory defaultFlowMessageFactory() {
- return LoggingSystem.getFlowMessageFactory();
+ return new DefaultFlowMessageFactory();
}
@SingletonFactory
@ConditionalOnMissingBinding
- public RecyclerFactory defaultRecyclerFactory() {
- return LoggingSystem.getRecyclerFactory();
+ public RecyclerFactoryProvider defaultRecyclerFactoryProvider(
+ final PropertyEnvironment environment,
+ final ClassLoader loader,
+ final @Named("StatusLogger") org.apache.logging.log4j.Logger statusLogger) {
+ final String factory = environment.getProperty(RecyclerProperties.class).factory();
+ final Stream providerStream = ServiceLoaderUtil.safeStream(
+ RecyclerFactoryProvider.class, ServiceLoader.load(RecyclerFactoryProvider.class, loader), statusLogger);
+ final Optional provider = factory != null
+ ? providerStream.filter(p -> factory.equals(p.getName())).findAny()
+ : providerStream.min(Comparator.comparing(RecyclerFactoryProvider::getOrder));
+ return provider.orElseGet(RecyclerFactoryProvider::getInstance);
+ }
+
+ @SingletonFactory
+ @ConditionalOnMissingBinding
+ public RecyclerFactory defaultRecyclerFactory(
+ final PropertyEnvironment environment, final RecyclerFactoryProvider provider) {
+ return provider.createForEnvironment(environment);
}
@SingletonFactory
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 fa1f47d5a3d..ab96103fec7 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
@@ -27,13 +27,13 @@
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.kit.recycler.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.message.ReusableMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.StringMap;
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 7a19db87b3f..e70d9faed23 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
@@ -26,10 +26,10 @@
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.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.plugins.Inject;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Garbage-free LogEventFactory that recycles mutable {@link LogEvent} instances.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java
new file mode 100644
index 00000000000..e024b2d2e66
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java
@@ -0,0 +1,188 @@
+/*
+ * 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.impl.internal;
+
+import org.apache.logging.log4j.kit.message.RecyclingMessageFactory;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ReusableObjectMessage;
+import org.apache.logging.log4j.message.ReusableParameterizedMessage;
+import org.apache.logging.log4j.message.ReusableSimpleMessage;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+/**
+ * Message factory that avoids allocating temporary objects where possible.
+ *
+ * Message instances are cached in a {@link Recycler} and reused when a new message is requested.
+ *
+ * Message instances are cached in a {@link Recycler} and reused when a new message is requested.
+ * @see Recycler
+ * @since 3.0.0
+ */
+@PerformanceSensitive("allocation")
+public final class ReusableMessageFactory implements RecyclingMessageFactory {
+
+ private final Recycler parameterizedMessageRecycler;
+ private final Recycler simpleMessageRecycler;
+ private final Recycler objectMessageRecycler;
+
+ public ReusableMessageFactory(final RecyclerFactory recyclerFactory) {
+ parameterizedMessageRecycler =
+ recyclerFactory.create(ReusableParameterizedMessage::new, ReusableParameterizedMessage::clear);
+ simpleMessageRecycler = recyclerFactory.create(ReusableSimpleMessage::new, ReusableSimpleMessage::clear);
+ objectMessageRecycler = recyclerFactory.create(ReusableObjectMessage::new, ReusableObjectMessage::clear);
+ }
+
+ @Override
+ public void recycle(final Message message) {
+ // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today!
+ if (message instanceof final ReusableParameterizedMessage reusable) {
+ reusable.clear();
+ parameterizedMessageRecycler.release(reusable);
+ } else if (message instanceof final ReusableObjectMessage reusable) {
+ reusable.clear();
+ objectMessageRecycler.release(reusable);
+ } else if (message instanceof final ReusableSimpleMessage reusable) {
+ reusable.clear();
+ simpleMessageRecycler.release(reusable);
+ }
+ }
+
+ @Override
+ public Message newMessage(final CharSequence charSequence) {
+ final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
+ result.set(charSequence);
+ return result;
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object... params) {
+ return parameterizedMessageRecycler.acquire().set(message, params);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0) {
+ return parameterizedMessageRecycler.acquire().set(message, p0);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0, final Object 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 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 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 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 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 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 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 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 parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
+ }
+
+ @Override
+ public Message newMessage(final String message) {
+ final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
+ result.set(message);
+ return result;
+ }
+
+ @Override
+ public Message newMessage(final Object message) {
+ final ReusableObjectMessage result = objectMessageRecycler.acquire();
+ result.set(message);
+ return result;
+ }
+}
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 06b301f7aee..fb1ec476cb5 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
@@ -24,9 +24,9 @@
import org.apache.logging.log4j.core.impl.CoreProperties.GarbageCollectionProperties;
import org.apache.logging.log4j.core.impl.LogEventFactory;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.plugins.PluginElement;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.StringBuilders;
/**
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 fcdc3adaa1f..24ce2d345a5 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
@@ -34,12 +34,12 @@
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.core.pattern.RegexReplacement;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
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 360dd56e06c..dc6f1607510 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
@@ -23,7 +23,7 @@
import java.nio.charset.CodingErrorAction;
import java.util.Objects;
import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
/**
* {@link Encoder} for {@link StringBuilder}s.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index d923c34f7cd..09cc6c7ddda 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -28,9 +28,9 @@
import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat;
import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat;
+import org.apache.logging.log4j.kit.recycler.Recycler;
import org.apache.logging.log4j.plugins.Namespace;
import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
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
deleted file mode 100644
index a60d6e4c9c3..00000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
+++ /dev/null
@@ -1,117 +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.core.util;
-
-import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.util.Lazy;
-
-/**
- * This class is borrowed from Jackson.
- */
-public final class JsonUtils {
-
- private static final char[] HC = "0123456789ABCDEF".toCharArray();
-
- /**
- * Read-only encoding table for first 128 Unicode code points (single-byte UTF-8 characters).
- * Value of 0 means "no escaping"; other positive values that value is character
- * to use after backslash; and negative values that generic (backslash - u)
- * escaping is to be used.
- */
- private static final Lazy ESC_CODES = Lazy.pure(() -> {
- final int[] table = new int[128];
- // Control chars need generic escape sequence
- for (int i = 0; i < 32; ++i) {
- // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with constant
- table[i] = -1;
- }
- /* Others (and some within that range too) have explicit shorter
- * sequences
- */
- table['"'] = '"';
- table['\\'] = '\\';
- // Escaping of slash is optional, so let's not add it
- table[0x08] = 'b';
- table[0x09] = 't';
- table[0x0C] = 'f';
- table[0x0A] = 'n';
- table[0x0D] = 'r';
- return table;
- });
-
- /**
- * Temporary buffer used for composing quote/escape sequences
- */
- private static final 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 = qbufRecycler.acquire();
- try {
- final int[] escCodes = ESC_CODES.get();
- 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;
- }
- }
- // 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);
- }
- } finally {
- qbufRecycler.release(qbuf);
- }
- }
-
- private static int _appendNumeric(final int value, final char[] qbuf) {
- qbuf[1] = 'u';
- // We know it's a control char, so only the last 2 chars are non-0
- qbuf[4] = HC[value >> 4];
- qbuf[5] = HC[value & 0xF];
- return 6;
- }
-
- private static int _appendNamed(final int esc, final char[] qbuf) {
- qbuf[1] = (char) esc;
- return 2;
- }
-}
diff --git a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
index 435dfde08d4..38ad23d59f9 100644
--- a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
+++ b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
@@ -17,17 +17,17 @@
package org.apache.logging.log4j.jctools;
import static java.util.Objects.requireNonNull;
-import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
import aQute.bnd.annotation.spi.ServiceProvider;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerProperties;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
import org.jctools.queues.MpmcArrayQueue;
/**
@@ -51,10 +51,7 @@ public String getName() {
@Override
public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
requireNonNull(environment, "environment");
- final int capacity = environment.getIntegerProperty("Recycler.capacity", DEFAULT_CAPACITY);
- if (capacity < 1) {
- throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity);
- }
+ final int capacity = environment.getProperty(RecyclerProperties.class).capacity();
return new JCToolsMpmcRecyclerFactory(capacity);
}
diff --git a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
index 51e9d9e272a..e5d121db0f2 100644
--- a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
+++ b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
@@ -20,15 +20,21 @@
import java.util.Comparator;
import java.util.List;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import java.util.ServiceLoader;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
import org.junit.jupiter.api.Test;
class JCToolsRecyclerFactoryProviderTest {
@Test
void verify_is_the_first() {
- final List> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
+ final List> providerClasses = ServiceLoaderUtil.safeStream(
+ RecyclerFactoryProvider.class,
+ ServiceLoader.load(
+ RecyclerFactoryProvider.class, getClass().getClassLoader()),
+ StatusLogger.getLogger())
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
.>map(RecyclerFactoryProvider::getClass)
.toList();
diff --git a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider
similarity index 100%
rename from log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
rename to log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
index 45a2a9312cc..3fbcfbd2bd4 100644
--- a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
@@ -23,14 +23,17 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.kit.logger.internal.DefaultLogBuilder;
+import org.apache.logging.log4j.kit.message.RecyclingMessageFactory;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
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.MessageFactory2;
import org.apache.logging.log4j.message.StringFormattedMessage;
import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.MessageFactory2Adapter;
import org.apache.logging.log4j.util.LambdaUtil;
import org.apache.logging.log4j.util.MessageSupplier;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -107,7 +110,7 @@ public abstract class AbstractLogger implements ExtendedLogger {
private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031
private final String name;
- private final MessageFactory messageFactory;
+ private final MessageFactory2 messageFactory;
private final FlowMessageFactory flowMessageFactory;
private final Recycler recycler;
private final Logger statusLogger;
@@ -125,7 +128,9 @@ protected AbstractLogger(
final RecyclerFactory recyclerFactory,
final Logger statusLogger) {
this.name = name;
- this.messageFactory = messageFactory;
+ this.messageFactory = messageFactory instanceof final MessageFactory2 messageFactory2
+ ? messageFactory2
+ : new MessageFactory2Adapter(messageFactory);
this.flowMessageFactory = flowMessageFactory;
this.recycler = recyclerFactory.create(DefaultLogBuilder::new);
this.statusLogger = statusLogger;
@@ -242,7 +247,9 @@ private void logMessageTrackRecursion(
// NOTE: This is a hot method. Current implementation compiles to 33 bytes of byte code.
// This is within the 35 byte MaxInlineSize threshold. Modify with care!
private void recycle(final @Nullable Message message) {
- messageFactory.recycle(message);
+ if (messageFactory instanceof final RecyclingMessageFactory recyclingMessageFactory) {
+ recyclingMessageFactory.recycle(message);
+ }
}
@PerformanceSensitive
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
index 47052fd14da..21a1cf92b3f 100644
--- a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
@@ -21,11 +21,11 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogBuilder;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerAware;
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.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerAware;
import org.apache.logging.log4j.util.LambdaUtil;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java
new file mode 100644
index 00000000000..74c33b6f6d9
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.kit.message;
+
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory2;
+
+/**
+ * A message factory backed by a {@link Recycler}.
+ *
+ * Messages acquired from this factory must be released using the {@link #recycle} method.
+ *
+ */
+public interface RecyclingMessageFactory extends MessageFactory2 {
+
+ /**
+ * Recycles a message back for potential reuse or cleanup.
+ *
+ *
+ *
+ * @see Recycler
+ */
+ void recycle(Message message);
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java
new file mode 100644
index 00000000000..a8f822b1764
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.message;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java
new file mode 100644
index 00000000000..4be05d49fdf
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.kit.recycler;
+
+/**
+ * Contract for recycling strategies.
+ * This is the primary building block for logging components striving for garbage-free operation.
+ *
+ * @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-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerKeys.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
similarity index 64%
rename from log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerKeys.java
rename to log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
index bc51e710148..aaaa6ce17ab 100644
--- a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerKeys.java
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
@@ -16,17 +16,13 @@
*/
package org.apache.logging.log4j.kit.recycler;
-import org.apache.logging.log4j.kit.env.Log4jProperty;
-import org.jspecify.annotations.Nullable;
-
-public class RecyclerKeys {
+/**
+ * Interface implemented by classes that need to interact with the {@link Recycler} that created them.
+ *
+ * @since 3.0.0
+ */
+@FunctionalInterface
+public interface RecyclerAware {
- /**
- * A set of common configuration options for recyclers
- *
- * @param factory The name of the recycler factory to use (cf. {@link RecyclerFactoryProvider#getName()}),
- * @param capacity The capacity of the recycler.
- */
- @Log4jProperty
- public record Recycler(@Nullable String factory, @Nullable Integer capacity) {}
+ void setRecycler(Recycler recycler);
}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java
new file mode 100644
index 00000000000..e209ef64979
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java
@@ -0,0 +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.kit.recycler;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contract for {@link Recycler} factories.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactory {
+
+ /**
+ * Creates a new recycler using the given supplier function for initial instances.
+ *
+ * @param supplier a function to provide initial instances
+ * @param the recyclable type
+ * @return a new recycler
+ */
+ default Recycler create(final Supplier supplier) {
+ return create(supplier, ignored -> {});
+ }
+
+ /**
+ * Creates a new recycler using the given supplier and cleaner functions.
+ *
+ * The provided supplier needs to make sure that generated instances are always clean.
+ *
+ *
+ * Recycled instances are always guaranteed to be clean.
+ * The cleaning of an instance can take place either just before acquisition or prior to admitting it back into the reusable instances pool.
+ * The moment when the cleaning will be carried out is implementation dependent.
+ * Though a released instance should ideally be cleaned immediately to avoid keeping references to unused objects.
+ *
+ *
+ * @param supplier a function to provide initial (and clean!) instances
+ * @param cleaner function to reset an instance before reuse
+ * @param the recyclable type
+ * @return a new recycler
+ */
+ Recycler create(Supplier supplier, Consumer cleaner);
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
new file mode 100644
index 00000000000..26ebd5900e9
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.kit.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider;
+
+/**
+ * Contract for providing {@link RecyclerFactory} instances.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactoryProvider {
+
+ static RecyclerFactoryProvider getInstance() {
+ return DummyRecyclerFactoryProvider.INSTANCE;
+ }
+
+ /**
+ * Denotes the value to be used while sorting recycler factory providers to determine the precedence order.
+ * Values will be sorted naturally, that is, lower values will imply higher precedence.
+ *
+ * @return the value to be used while sorting
+ */
+ default int getOrder() {
+ return 0;
+ }
+
+ /**
+ * The name of this recycler factory provider.
+ * Recycler factory providers are required to have unique names.
+ *
+ * @return the name of this recycler factory provider
+ */
+ String getName();
+
+ /**
+ * Creates a recycler factory for the provided environment.
+ *
+ * The return value can be null indicating that the recycler factory is not available for the provided environment.
+ * For instance, the provider of a {@link ThreadLocal}-based recycler factory can return null if the environment is of a web application.
+ *
+ *
+ * @param environment an environment
+ * @return either a recycler factory instance, or null, if the associated recycler factory is not available for the given environment
+ */
+ @Nullable
+ RecyclerFactory createForEnvironment(PropertyEnvironment environment);
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java
new file mode 100644
index 00000000000..71dd1b2f190
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java
@@ -0,0 +1,53 @@
+/*
+ * 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.kit.recycler;
+
+import org.apache.logging.log4j.kit.env.Log4jProperty;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A set of common configuration options for recyclers
+ *
+ * @param factory The name of the recycler factory to use (cf. {@link RecyclerFactoryProvider#getName()}),
+ * @param capacity The capacity of the recycler.
+ */
+@NullMarked
+@Log4jProperty(name = "recycler")
+public record RecyclerProperties(@Nullable String factory, @Nullable Integer capacity) {
+ /**
+ * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting the number of available processors
+ */
+ private static final int DEFAULT_CAPACITY =
+ Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
+
+ public RecyclerProperties {
+ capacity = validateCapacity(capacity);
+ }
+
+ private static Integer validateCapacity(final @Nullable Integer capacity) {
+ if (capacity != null) {
+ if (capacity >= 1) {
+ return capacity;
+ }
+ StatusLogger.getLogger()
+ .warn("Invalid recycler capacity {}, using default capacity {}.", capacity, DEFAULT_CAPACITY);
+ }
+ return DEFAULT_CAPACITY;
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java
new file mode 100644
index 00000000000..973bed589b6
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.kit.recycler.internal;
+
+import java.util.AbstractQueue;
+import java.util.Iterator;
+import java.util.stream.IntStream;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.apache.logging.log4j.util.InternalApi;
+
+/**
+ * An array-backed, fixed-length, not-thread-safe {@link java.util.Queue} implementation.
+ *
+ * @param the element type
+ */
+@InternalApi
+@NotThreadSafe
+final class ArrayQueue extends AbstractQueue {
+
+ private final E[] buffer;
+
+ private int head;
+
+ private int tail;
+
+ private int size;
+
+ @SuppressWarnings("unchecked")
+ ArrayQueue(final int capacity) {
+ if (capacity < 1) {
+ throw new IllegalArgumentException("invalid capacity: " + capacity);
+ }
+ buffer = (E[]) new Object[capacity];
+ head = 0;
+ tail = -1;
+ size = 0;
+ }
+
+ @Override
+ public Iterator iterator() {
+ int[] i = {head};
+ return IntStream.range(0, size)
+ .mapToObj(ignored -> {
+ final E item = buffer[i[0]];
+ i[0] = (i[0] + 1) % buffer.length;
+ return item;
+ })
+ .iterator();
+ }
+
+ @Override
+ public boolean offer(final E item) {
+ if (size == buffer.length) {
+ return false;
+ }
+ tail = (tail + 1) % buffer.length;
+ buffer[tail] = item;
+ size++;
+ return true;
+ }
+
+ @Override
+ public E poll() {
+ if (isEmpty()) {
+ return null;
+ }
+ final E item = buffer[head];
+ buffer[head] = null; // Clear refs for GC
+ head = (head + 1) % buffer.length;
+ size--;
+ return item;
+ }
+
+ @Override
+ public E peek() {
+ if (isEmpty()) {
+ return null;
+ }
+ return buffer[head];
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
new file mode 100644
index 00000000000..8a58bbcac0c
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler does not recycle anything; all instances are freshly created.
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class DummyRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ public static final RecyclerFactoryProvider INSTANCE = new DummyRecyclerFactoryProvider();
+
+ @Override
+ public int getOrder() {
+ return 900;
+ }
+
+ @Override
+ public String getName() {
+ return "dummy";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ return DummyRecyclerFactory.INSTANCE;
+ }
+
+ // Visible for testing
+ static final class DummyRecyclerFactory implements RecyclerFactory {
+
+ private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory();
+
+ private DummyRecyclerFactory() {}
+
+ @Override
+ public Recycler create(final Supplier supplier, final Consumer cleaner) {
+ requireNonNull(supplier, "supplier");
+ return new DummyRecycler<>(supplier);
+ }
+
+ private static final class DummyRecycler extends AbstractRecycler {
+
+ private DummyRecycler(final Supplier supplier) {
+ super(supplier);
+ }
+
+ @Override
+ public V acquire() {
+ return createInstance();
+ }
+
+ @Override
+ public void release(final V value) {}
+ }
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java
new file mode 100644
index 00000000000..e124575db32
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java
@@ -0,0 +1,103 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerProperties;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in a fixed-size queue.
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class QueueingRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ @Override
+ public int getOrder() {
+ return 800;
+ }
+
+ @Override
+ public String getName() {
+ return "queue";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ requireNonNull(environment, "environment");
+
+ final int capacity = environment.getProperty(RecyclerProperties.class).capacity();
+ return new QueueingRecyclerFactory(capacity);
+ }
+
+ // Visible for testing
+ static final class QueueingRecyclerFactory implements RecyclerFactory {
+
+ // Visible for testing
+ final int capacity;
+
+ private QueueingRecyclerFactory(int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ public Recycler create(final Supplier supplier, final Consumer cleaner) {
+ requireNonNull(supplier, "supplier");
+ requireNonNull(cleaner, "cleaner");
+ final Queue queue = new ArrayBlockingQueue<>(capacity);
+ return new QueueingRecycler<>(supplier, cleaner, queue);
+ }
+
+ // Visible for testing
+ static final class QueueingRecycler extends AbstractRecycler {
+
+ private final Consumer cleaner;
+
+ // Visible for testing
+ final Queue queue;
+
+ private QueueingRecycler(final Supplier supplier, final Consumer cleaner, final Queue queue) {
+ super(supplier);
+ this.cleaner = cleaner;
+ this.queue = queue;
+ }
+
+ @Override
+ public V acquire() {
+ final V value = queue.poll();
+ return value != null ? value : createInstance();
+ }
+
+ @Override
+ public void release(final V value) {
+ requireNonNull(value, "value");
+ cleaner.accept(value);
+ queue.offer(value);
+ }
+ }
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java
new file mode 100644
index 00000000000..4bb57edb167
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java
@@ -0,0 +1,118 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.util.LoaderUtil.isClassAvailable;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerProperties;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in a fixed-size 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 QueueingRecyclerFactoryProvider}.
+ *
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class ThreadLocalRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ private static final boolean SERVLET_API_PRESENT =
+ isClassAvailable("javax.servlet.Servlet") || isClassAvailable("jakarta.servlet.Servlet");
+
+ @Override
+ public int getOrder() {
+ return SERVLET_API_PRESENT ? Integer.MAX_VALUE : 700;
+ }
+
+ @Override
+ public String getName() {
+ return "threadLocal";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ requireNonNull(environment, "environment");
+ final int capacity = environment.getProperty(RecyclerProperties.class).capacity();
+ return new ThreadLocalRecyclerFactory(capacity);
+ }
+
+ // Visible for testing
+ static final class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+ /**
+ * Maximum number of objects retained per thread.
+ *
+ * This allows to acquire objects in recursive method calls and maintain minimal overhead in the scenarios where the active instance count goes far beyond this for a brief moment.
+ *
+ */
+ // Visible for testing
+ final int capacity;
+
+ private ThreadLocalRecyclerFactory(int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ public Recycler create(final Supplier supplier, final Consumer cleaner) {
+ requireNonNull(supplier, "supplier");
+ requireNonNull(cleaner, "cleaner");
+ return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
+ }
+
+ // Visible for testing
+ static final class ThreadLocalRecycler extends AbstractRecycler {
+
+ private final Consumer cleaner;
+
+ // Visible for testing
+ final ThreadLocal> queueRef;
+
+ private ThreadLocalRecycler(final Supplier supplier, final Consumer cleaner, final int capacity) {
+ super(supplier);
+ this.queueRef = ThreadLocal.withInitial(() -> new ArrayQueue<>(capacity));
+ this.cleaner = cleaner;
+ }
+
+ @Override
+ public V acquire() {
+ final Queue queue = queueRef.get();
+ final V value = queue.poll();
+ return value != null ? value : createInstance();
+ }
+
+ @Override
+ public void release(final V value) {
+ requireNonNull(value, "value");
+ cleaner.accept(value);
+ final Queue queue = queueRef.get();
+ queue.offer(value);
+ }
+ }
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java
new file mode 100644
index 00000000000..8d28b85cb9d
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java
@@ -0,0 +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.
+ */
+/**
+ * Internal interfaces and classes to be used by authors of logging implementations or for internal use by
+ * API classes.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.recycler;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java
new file mode 100644
index 00000000000..764d7ec8d7a
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java
@@ -0,0 +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.kit.recycler.support;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerAware;
+
+/**
+ * Abstract implementation of {@link Recycler} that properly handles {@link RecyclerAware} objects
+ *
+ * @param The type of recycled object.
+ * @since 3.0.0
+ */
+public abstract class AbstractRecycler implements Recycler {
+
+ private final Supplier supplier;
+
+ protected AbstractRecycler(final Supplier supplier) {
+ this.supplier = requireNonNull(supplier, "supplier");
+ }
+
+ protected final V createInstance() {
+ final V instance = supplier.get();
+ if (instance instanceof RecyclerAware) {
+ @SuppressWarnings("unchecked")
+ final RecyclerAware recyclerAware = (RecyclerAware) instance;
+ recyclerAware.setRecycler(this);
+ }
+ return instance;
+ }
+}
diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java
new file mode 100644
index 00000000000..215ceeb7921
--- /dev/null
+++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ * Support classes for the creation of recyclers.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.recycler.support;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java
new file mode 100644
index 00000000000..7c1c63be583
--- /dev/null
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java
@@ -0,0 +1,41 @@
+/*
+ * 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.kit.env;
+
+import java.util.Map;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.kit.env.support.BasicPropertyEnvironment;
+import org.apache.logging.log4j.status.StatusLogger;
+
+public class TestPropertyEnvironment extends BasicPropertyEnvironment {
+
+ private final Map props;
+
+ public TestPropertyEnvironment(final Map props) {
+ this(props, StatusLogger.getLogger());
+ }
+
+ public TestPropertyEnvironment(final Map props, final Logger logger) {
+ super(logger);
+ this.props = props;
+ }
+
+ @Override
+ public String getStringProperty(final String name) {
+ return props.get(name);
+ }
+}
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
index 22949a36a96..9298476c525 100644
--- a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
@@ -31,12 +31,11 @@
import java.util.TimeZone;
import java.util.stream.Stream;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.kit.env.Log4jProperty;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.TestPropertyEnvironment;
import org.apache.logging.log4j.kit.logger.TestListLogger;
import org.apache.logging.log4j.spi.StandardLevel;
-import org.apache.logging.log4j.status.StatusLogger;
import org.assertj.core.api.Assertions;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
@@ -261,23 +260,4 @@ private void assertMapConvertsTo(final Map map, final Object exp
assertThat(actual).isEqualTo(expected);
assertThat(logger.getMessages()).isEmpty();
}
-
- private static class TestPropertyEnvironment extends BasicPropertyEnvironment {
-
- private final Map props;
-
- public TestPropertyEnvironment(final Map props) {
- this(props, StatusLogger.getLogger());
- }
-
- public TestPropertyEnvironment(final Map props, final Logger logger) {
- super(logger);
- this.props = props;
- }
-
- @Override
- public String getStringProperty(final String name) {
- return props.get(name);
- }
- }
}
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
index acf6f3689d0..9595c9bfa2a 100644
--- a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
@@ -21,13 +21,13 @@
import java.util.List;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.internal.recycler.DummyRecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider;
import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
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.ParameterizedNoReferenceMessageFactory;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.java
new file mode 100644
index 00000000000..40e6ac557ff
--- /dev/null
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.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.kit.recycler.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ArrayBlockingQueue;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ArrayQueueTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0})
+ void invalid_capacity_should_not_be_allowed(final int invalidCapacity) {
+ assertThatThrownBy(() -> new ArrayQueue<>(invalidCapacity))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("invalid capacity: " + invalidCapacity);
+ }
+
+ @Test
+ void should_work_with_capacity_1() {
+
+ // Verify initials
+ final Queue queue = new ArrayQueue<>(1);
+ assertThat(queue.size()).isEqualTo(0);
+ assertThat(queue.peek()).isNull();
+ assertThat(queue.poll()).isNull();
+ assertThat(queue).isEmpty();
+
+ // Verify enqueue & deque
+ assertThat(queue.offer("foo")).isTrue();
+ assertThat(queue.offer("bar")).isFalse();
+ assertThat(queue.size()).isEqualTo(1);
+ assertThat(queue).containsOnly("foo");
+ assertThat(queue.peek()).isEqualTo("foo");
+ assertThat(queue.poll()).isEqualTo("foo");
+
+ // Verify final state
+ assertThat(queue.size()).isEqualTo(0);
+ assertThat(queue.peek()).isNull();
+ assertThat(queue.poll()).isNull();
+ assertThat(queue).isEmpty();
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "1,0.3", "1,0.5", "1,0.8", "2,0.3", "2,0.5", "2,0.8", "3,0.3", "3,0.5", "3,0.8", "4,0.3", "4,0.5", "4,0.8"
+ })
+ void ops_should_match_with_std_lib(final int capacity, final double pollRatio) {
+
+ // Set the stage
+ final Random random = new Random(0);
+ final int opCount = random.nextInt(100);
+ final Queue queueRef = new ArrayBlockingQueue<>(capacity);
+ final Queue queueTarget = new ArrayQueue<>(capacity);
+
+ for (int opIndex = 0; opIndex < opCount; opIndex++) {
+
+ // Verify entry
+ assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+ assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+ assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+
+ // Is this a `poll()`?
+ if (pollRatio >= random.nextDouble()) {
+ assertThat(queueTarget.poll()).isEqualTo(queueRef.poll());
+ }
+
+ // Then this is an `offer()`
+ else {
+ final String item = "op@" + opIndex;
+ assertThat(queueTarget.offer(item)).isEqualTo(queueRef.offer(item));
+ }
+
+ // Verify exit
+ assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+ assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+ assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+ }
+ }
+}
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java
new file mode 100644
index 00000000000..cdc857e734e
--- /dev/null
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider.DummyRecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.internal.QueueingRecyclerFactoryProvider.QueueingRecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+public class RecyclerFactoryRegistryTest {
+
+ private static final int DEFAULT_CAPACITY =
+ Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
+
+ @Test
+ void DummyRecyclerFactory_should_work() {
+ final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("dummy", null);
+ assertThat(factory).isInstanceOf(DummyRecyclerFactory.class);
+ }
+
+ @Test
+ void ThreadLocalRecyclerFactory_should_work() {
+ final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("threadLocal", null);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(DEFAULT_CAPACITY);
+ }
+
+ @Test
+ void ThreadLocalRecyclerFactory_should_work_with_capacity() {
+ final int capacity = 13;
+ final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("threadLocal", capacity);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(capacity);
+ }
+
+ @Test
+ void QueueingRecyclerFactory_should_work() {
+ final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("queue", null);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(DEFAULT_CAPACITY);
+ }
+
+ @Test
+ void QueueingRecyclerFactory_should_work_with_capacity() {
+ final int capacity = 100;
+ final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("queue", capacity);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(capacity);
+ }
+
+ @Test
+ void verify_order() {
+ final RecyclerFactoryProvider dummyProvider = new DummyRecyclerFactoryProvider();
+ final RecyclerFactoryProvider threadLocalProvider = new ThreadLocalRecyclerFactoryProvider();
+ final RecyclerFactoryProvider queueProvider = new QueueingRecyclerFactoryProvider();
+ assertThat(dummyProvider.getOrder()).isGreaterThan(queueProvider.getOrder());
+ assertThat(queueProvider.getOrder()).isGreaterThan(threadLocalProvider.getOrder());
+ }
+}
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.java
new file mode 100644
index 00000000000..98b00bf37b8
--- /dev/null
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.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.kit.recycler.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.TestPropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+import org.jspecify.annotations.Nullable;
+
+final class RecyclerFactoryTestUtil {
+
+ private RecyclerFactoryTestUtil() {}
+
+ static @Nullable RecyclerFactory createForEnvironment(final String factory, final @Nullable Integer capacity) {
+ final Map properties = new HashMap<>();
+ properties.put("recycler.factory", factory);
+ if (capacity != null) {
+ properties.put("recycler.capacity", capacity.toString());
+ }
+ final PropertyEnvironment env = new TestPropertyEnvironment(properties);
+ return ServiceLoaderUtil.safeStream(
+ RecyclerFactoryProvider.class,
+ ServiceLoader.load(
+ RecyclerFactoryProvider.class, RecyclerFactoryTestUtil.class.getClassLoader()),
+ StatusLogger.getLogger())
+ .filter(p -> factory.equals(p.getName()))
+ .findFirst()
+ .map(p -> p.createForEnvironment(env))
+ .orElse(null);
+ }
+}
diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java
new file mode 100644
index 00000000000..f33afb1040c
--- /dev/null
+++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory.ThreadLocalRecycler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junitpioneer.jupiter.params.IntRangeSource;
+
+class ThreadLocalRecyclerFactoryProviderTest {
+
+ private static final int CAPACITY = 13;
+
+ private static class RecyclableObject {}
+
+ private ThreadLocalRecycler recycler;
+
+ private Queue recyclerQueue;
+
+ @BeforeEach
+ void setUp() {
+ final RecyclerFactory recyclerFactory = RecyclerFactoryTestUtil.createForEnvironment("threadLocal", CAPACITY);
+ assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class);
+ assert recyclerFactory != null;
+ recycler = (ThreadLocalRecycler) recyclerFactory.create(RecyclableObject::new);
+ recyclerQueue = recycler.queueRef.get();
+ }
+
+ @ParameterizedTest
+ @IntRangeSource(from = 1, to = CAPACITY, closed = true)
+ void nested_acquires_should_not_interfere(final int acquisitionCount) {
+
+ // pool should start empty
+ assertThat(recyclerQueue).isEmpty();
+
+ final List acquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .collect(Collectors.toList());
+
+ // still nothing returned to pool
+ assertThat(recyclerQueue).isEmpty();
+
+ // don't want any duplicate instances
+ assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+ acquiredObjects.forEach(recycler::release);
+
+ // and now they should be back in the pool
+ assertThat(recyclerQueue).hasSize(acquisitionCount);
+
+ // then reacquire them to see that they're still the same object as we've filled in
+ // the thread-local queue with returned objects
+ final List reacquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .collect(Collectors.toList());
+
+ assertThat(reacquiredObjects).containsExactlyElementsOf(acquiredObjects);
+ }
+
+ @Test
+ void nested_acquires_past_max_queue_size_should_discard_extra_releases() {
+
+ assertThat(recyclerQueue).isEmpty();
+
+ // Simulate a callstack with excessive logging
+ final int acquisitionCount = Math.addExact(CAPACITY, 1024);
+ final List acquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .toList();
+
+ // Verify collected instances are all new
+ assertThat(acquiredObjects).doesNotHaveDuplicates();
+
+ // Verify the pool is still empty
+ assertThat(recyclerQueue).isEmpty();
+
+ // Release all acquired instances
+ acquiredObjects.forEach(recycler::release);
+
+ // Verify the queue size is capped
+ assertThat(recyclerQueue).hasSize(CAPACITY);
+ }
+}
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 65d7cb448ab..128b619128d 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
@@ -33,6 +33,7 @@
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.Encoder;
import org.apache.logging.log4j.core.layout.StringBuilderEncoder;
+import org.apache.logging.log4j.kit.recycler.Recycler;
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.EventResolverInterceptor;
@@ -48,7 +49,6 @@
import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.recycler.Recycler;
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/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
index 77a80af09cb..4ce28da6a71 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
@@ -22,8 +22,8 @@
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.recycler.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/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 0f7fc723ccf..cda477d2159 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
@@ -17,11 +17,11 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterConsumer;
import org.apache.logging.log4j.message.ParameterVisitable;
-import org.apache.logging.log4j.spi.recycler.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 64356bfe2ec..603692bf7c3 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
@@ -21,9 +21,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
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 8d505eff8f4..dddf66f7277 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
@@ -22,11 +22,11 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.layout.template.json.util.CharSequencePointer;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Exception stack trace to JSON string resolver used by {@link ExceptionResolver}.
diff --git a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
deleted file mode 100644
index c66b5c946ab..00000000000
--- a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.logging.slf4j.SLF4JProvider
\ No newline at end of file