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 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