This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch fix/move-recycler-to-kit in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 397e2103a5614ad358a7ed98a4c135c9a30089bf Author: Piotr P. Karwasz <[email protected]> AuthorDate: Thu Mar 14 18:46:58 2024 +0100 Move `Recycler` to `log4j-kit` --- .../log4j/message/ReusableMessageFactory.java | 2 +- .../message/ReusableParameterizedMessage.java | 2 +- .../message/StringFormatterMessageFactory.java | 2 +- .../MessageFactory2Adapter.java} | 128 ++++++++------------- .../logging/log4j/util/ServiceLoaderUtil.java | 7 ++ .../logging/log4j/async/logger/AsyncLogger.java | 4 +- .../org/apache/logging/log4j/core/LoggerTest.java | 4 +- .../logging/log4j/core/util/JsonUtilsTest.java | 68 ----------- .../java/org/apache/logging/log4j/core/Logger.java | 2 +- .../apache/logging/log4j/core/LoggerContext.java | 31 ++--- .../logging/log4j/core/config/Configuration.java | 2 +- .../log4j/core/filter/StructuredDataFilter.java | 4 +- .../logging/log4j/core/impl/DefaultBundle.java | 40 +++++-- .../logging/log4j/core/impl/MutableLogEvent.java | 2 +- .../log4j/core/impl/ReusableLogEventFactory.java | 4 +- .../impl/internal}/ReusableMessageFactory.java | 99 ++++------------ .../log4j/core/layout/AbstractStringLayout.java | 4 +- .../logging/log4j/core/layout/PatternLayout.java | 2 +- .../log4j/core/layout/StringBuilderEncoder.java | 2 +- .../log4j/core/pattern/DatePatternConverter.java | 2 +- .../apache/logging/log4j/core/util/JsonUtils.java | 117 ------------------- .../jctools/JCToolsRecyclerFactoryProvider.java | 20 ++-- .../JCToolsRecyclerFactoryProviderTest.java | 12 +- ...ing.log4j.kit.recycler.RecyclerFactoryProvider} | 0 .../logging/log4j/kit/logger/AbstractLogger.java | 17 ++- .../kit/logger/internal/DefaultLogBuilder.java | 4 +- .../log4j/kit/message/RecyclingMessageFactory.java | 36 +++--- .../logging/log4j/kit/message/package-info.java | 22 ++++ .../logging/log4j/kit/recycler/Recycler.java | 48 ++++++++ .../logging/log4j/kit/recycler/RecyclerAware.java | 27 ++--- .../log4j/kit/recycler/RecyclerFactory.java | 58 ++++++++++ .../kit/recycler/RecyclerFactoryProvider.java | 64 +++++++++++ .../log4j/kit/recycler/internal/ArrayQueue.java | 100 ++++++++++++++++ .../internal/DummyRecyclerFactoryProvider.java | 83 +++++++++++++ .../internal/QueueingRecyclerFactoryProvider.java | 50 ++++---- .../ThreadLocalRecyclerFactoryProvider.java | 123 ++++++++++++++++++++ .../logging/log4j/kit/recycler/package-info.java | 26 +++++ .../kit/recycler/support/AbstractRecycler.java | 48 ++++++++ .../log4j/kit/recycler/support/package-info.java | 25 ++++ .../log4j/kit/env/TestPropertyEnvironment.java | 34 +++--- .../env/support/BasicPropertyEnvironmentTest.java | 22 +--- .../logging/log4j/kit/logger/TestListLogger.java | 4 +- .../kit/recycler/internal/ArrayQueueTest.java | 101 ++++++++++++++++ .../internal/RecyclerFactoryRegistryTest.java | 84 ++++++++++++++ .../recycler/internal/RecyclerFactoryTestUtil.java | 51 ++++++++ .../ThreadLocalRecyclerFactoryProviderTest.java | 105 +++++++++++++++++ .../layout/template/json/JsonTemplateLayout.java | 2 +- .../template/json/resolver/CounterResolver.java | 2 +- .../json/resolver/MessageParameterResolver.java | 2 +- .../json/resolver/ReadOnlyStringMapResolver.java | 4 +- .../json/resolver/StackTraceStringResolver.java | 4 +- .../services/org.apache.logging.log4j.spi.Provider | 1 - 52 files changed, 1203 insertions(+), 504 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index 6ec36d5308..93c0f27f63 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 @@ import org.apache.logging.log4j.util.PerformanceSensitive; * @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 ea074c5db1..166fd213c8 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 @@ public class ReusableParameterizedMessage implements ReusableMessage, ParameterV 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 7ce4296ba5..855340a197 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 @@ package org.apache.logging.log4j.message; * This class implements all {@link MessageFactory} methods. * </p> */ -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/message/StringFormatterMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java similarity index 57% copy from log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java copy to log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java index 7ce4296ba5..1e0f4162c0 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java @@ -14,97 +14,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.message; +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; /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. - * <p> - * Enables the use of {@link java.util.Formatter} strings in message strings. - * </p> - * <p> - * Creates {@link StringFormattedMessage} instances for {@link #newMessage(String, Object...)}. - * </p> - * <p> - * This class is immutable. - * </p> - * <p> - * <strong>Note to implementors:</strong> - * </p> - * <p> - * This class implements all {@link MessageFactory} methods. - * </p> + * Adapts a legacy MessageFactory to the new MessageFactory2 interface. + * + * @since 2.6 */ -public final class StringFormatterMessageFactory implements MessageFactory { - - /** - * Instance of StringFormatterMessageFactory. - */ - public static final StringFormatterMessageFactory INSTANCE = new StringFormatterMessageFactory(); - - /** - * Constructs a message factory with default flow strings. - */ - public StringFormatterMessageFactory() {} - - /** - * Creates {@link StringFormattedMessage} instances. - * - * @param message The message pattern. - * @param params The parameters to the message. - * @return The Message. - * - * @see MessageFactory#newMessage(String, Object...) - */ +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 String message, final Object... params) { - return new StringFormattedMessage(message, params); + public Message newMessage(final CharSequence charSequence) { + return new SimpleMessage(charSequence); } - /** - * @since 2.6.1 - */ @Override public Message newMessage(final String message, final Object p0) { - return new StringFormattedMessage(message, p0); + return wrapped.newMessage(message, p0); } - /** - * @since 2.6.1 - */ @Override public Message newMessage(final String message, final Object p0, final Object p1) { - return new StringFormattedMessage(message, p0, p1); + return wrapped.newMessage(message, p0, p1); } - /** - * @since 2.6.1 - */ @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { - return new StringFormattedMessage(message, p0, p1, p2); + return wrapped.newMessage(message, p0, p1, p2); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, final Object p0, final Object p1, final Object p2, final Object p3) { - return new StringFormattedMessage(message, p0, p1, p2, p3); + return wrapped.newMessage(message, p0, p1, p2, p3); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4); + return wrapped.newMessage(message, p0, p1, p2, p3, p4); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, @@ -114,12 +79,9 @@ public final class StringFormatterMessageFactory implements MessageFactory { final Object p3, final Object p4, final Object p5) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5); + return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, @@ -130,12 +92,9 @@ public final class StringFormatterMessageFactory implements MessageFactory { final Object p4, final Object p5, final Object p6) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6); + return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, @@ -147,12 +106,9 @@ public final class StringFormatterMessageFactory implements MessageFactory { final Object p5, final Object p6, final Object p7) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); + return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, @@ -165,12 +121,9 @@ public final class StringFormatterMessageFactory implements MessageFactory { final Object p6, final Object p7, final Object p8) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } - /** - * @since 2.6.1 - */ @Override public Message newMessage( final String message, @@ -184,6 +137,21 @@ public final class StringFormatterMessageFactory implements MessageFactory { final Object p7, final Object p8, final Object p9) { - return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, 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 7fe952cbd0..3302c65323 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.Spliterators; 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 final class ServiceLoaderUtil { .filter(service -> classes.add(service.getClass())); } + // Available in Log4j API 2.x + public static <S> Stream<S> safeStream( + final Class<S> ignoredServiceType, final ServiceLoader<S> serviceLoader, final Logger ignoredStatusLogger) { + return safeStream(serviceLoader); + } + private static class ServiceLoaderSpliterator<S> extends Spliterators.AbstractSpliterator<S> { private final Iterator<S> 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 80578ee369..cd430f94c0 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.impl.ContextDataFactory; 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 49f3058956..e4c5c95dfe 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.ReusableMessageFactory; 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 5f035e6239..0000000000 --- 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 <a href="https://github.com/FasterXML/jackson-core">Jackson</a>. - */ -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 09a6d89482..8964b642d9 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.LoggerConfig; 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 3f1b4d9e5d..8e558f42d7 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<LoggerContext> KEY = Key.forClass(LoggerContext.class); private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>(); + private final MessageFactory defaultMessageFactory; + private final Collection<Consumer<Configuration>> configurationStartedListeners = new ArrayList<>(); private final Collection<Consumer<Configuration>> configurationStoppedListeners = new ArrayList<>(); private final Lazy<List<LoggerContextShutdownAware>> listeners = Lazy.relaxed(CopyOnWriteArrayList::new); @@ -139,6 +141,7 @@ public class LoggerContext extends AbstractLifeCycle 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 class LoggerContext extends AbstractLifeCycle * @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( @@ -540,7 +543,7 @@ public class LoggerContext extends AbstractLifeCycle */ @Override public Logger getLogger(final String name) { - return getLogger(name, null); + return getLogger(name, defaultMessageFactory); } /** @@ -566,13 +569,14 @@ public class LoggerContext extends AbstractLifeCycle */ @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()); } @@ -606,7 +610,7 @@ public class LoggerContext extends AbstractLifeCycle */ @Override public boolean hasLogger(final String name) { - return loggerRegistry.hasLogger(name); + return loggerRegistry.hasLogger(name, defaultMessageFactory); } /** @@ -910,13 +914,12 @@ public class LoggerContext extends AbstractLifeCycle 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 933456f6b1..b0e063fb88 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.time.NanoClock; 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 2a21b56b22..f9b66888fb 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.Logger; 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.Plugin; 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/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java index 2f176ea286..31ff01bacb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java @@ -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; @@ -27,6 +31,7 @@ import org.apache.logging.log4j.core.config.DefaultConfigurationFactory; 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; @@ -40,6 +45,11 @@ import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.core.time.internal.DummyNanoClock; 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.RecyclerKeys; +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.Named; @@ -49,11 +59,10 @@ import org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.spi.CopyOnWrite; import org.apache.logging.log4j.spi.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. @@ -84,20 +93,37 @@ public class DefaultBundle { @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(RecyclerKeys.Recycler.class).factory(); + final Stream<RecyclerFactoryProvider> providerStream = ServiceLoaderUtil.safeStream( + RecyclerFactoryProvider.class, ServiceLoader.load(RecyclerFactoryProvider.class, loader), statusLogger); + final Optional<RecyclerFactoryProvider> 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 fa1f47d5a3..ab96103fec 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.Instant; 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 7a19db87b3..e70d9faed2 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.ReusableLogEvent; 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-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java similarity index 67% copy from log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java copy to log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java index 6ec36d5308..e024b2d2e6 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java @@ -14,78 +14,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.message; - -import org.apache.logging.log4j.spi.LoggingSystem; -import org.apache.logging.log4j.spi.recycler.Recycler; -import org.apache.logging.log4j.spi.recycler.RecyclerFactory; +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; /** - * Implementation of the {@link MessageFactory} interface that avoids allocating temporary objects where possible. + * Message factory that avoids allocating temporary objects where possible. + * <p> + * Message instances are cached in a {@link Recycler} and reused when a new message is requested. + * </p> * Message instances are cached in a {@link Recycler} and reused when a new message is requested. - * Messages returned from this factory must be {@linkplain #recycle(Message) recycled} when done using. - * @see ReusableSimpleMessage - * @see ReusableObjectMessage - * @see ReusableParameterizedMessage * @see Recycler - * @since 2.6 + * @since 3.0.0 */ @PerformanceSensitive("allocation") -public final class ReusableMessageFactory implements MessageFactory { - - /** - * Instance of {@link ReusableMessageFactory}. - */ - public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory(); +public final class ReusableMessageFactory implements RecyclingMessageFactory { private final Recycler<ReusableParameterizedMessage> parameterizedMessageRecycler; private final Recycler<ReusableSimpleMessage> simpleMessageRecycler; private final Recycler<ReusableObjectMessage> objectMessageRecycler; - /** - * Constructs a message factory using the default {@link RecyclerFactory}. - */ - public ReusableMessageFactory() { - this(LoggingSystem.getRecyclerFactory()); - } - public ReusableMessageFactory(final RecyclerFactory recyclerFactory) { - super(); parameterizedMessageRecycler = recyclerFactory.create(ReusableParameterizedMessage::new, ReusableParameterizedMessage::clear); simpleMessageRecycler = recyclerFactory.create(ReusableSimpleMessage::new, ReusableSimpleMessage::clear); objectMessageRecycler = recyclerFactory.create(ReusableObjectMessage::new, ReusableObjectMessage::clear); } - /** - * Invokes {@link ReusableMessage#clear()} when possible. - * This flag is used internally to verify that a reusable message is no longer in use and - * can be reused. - * @param message the message to make available again - * @since 2.7 - */ - @SuppressWarnings("removal") - public static void release(final Message message) { // LOG4J2-1583 - if (message instanceof ReusableMessage) { - ((ReusableMessage) message).clear(); - } else if (message instanceof Clearable) { - ((Clearable) message).clear(); - } - } - @Override public void recycle(final Message message) { - if (message instanceof ReusableMessage) { - ((ReusableMessage) message).clear(); - } // related to LOG4J2-1583 and nested log messages clobbering each other. recycle messages today! - if (message instanceof ReusableParameterizedMessage) { - parameterizedMessageRecycler.release((ReusableParameterizedMessage) message); - } else if (message instanceof ReusableObjectMessage) { - objectMessageRecycler.release((ReusableObjectMessage) message); - } else if (message instanceof ReusableSimpleMessage) { - simpleMessageRecycler.release((ReusableSimpleMessage) message); + 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); } } @@ -96,15 +70,6 @@ public final class ReusableMessageFactory implements MessageFactory { return result; } - /** - * Creates {@link ReusableParameterizedMessage} instances. - * - * @param message The message pattern. - * @param params The message parameters. - * @return The Message. - * - * @see MessageFactory#newMessage(String, Object...) - */ @Override public Message newMessage(final String message, final Object... params) { return parameterizedMessageRecycler.acquire().set(message, params); @@ -207,14 +172,6 @@ public final class ReusableMessageFactory implements MessageFactory { return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } - /** - * Creates {@link ReusableSimpleMessage} instances. - * - * @param message The message String. - * @return The Message. - * - * @see MessageFactory#newMessage(String) - */ @Override public Message newMessage(final String message) { final ReusableSimpleMessage result = simpleMessageRecycler.acquire(); @@ -222,14 +179,6 @@ public final class ReusableMessageFactory implements MessageFactory { return result; } - /** - * Creates {@link ReusableObjectMessage} instances. - * - * @param message The message Object. - * @return The Message. - * - * @see MessageFactory#newMessage(Object) - */ @Override public Message newMessage(final Object message) { final ReusableObjectMessage result = objectMessageRecycler.acquire(); 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 ed6953066c..0bfb52df1c 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.config.LoggerConfig; import org.apache.logging.log4j.core.impl.CoreKeys; 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 c7c238078b..cc41b925c6 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.PatternFormatter; 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 360dd56e06..dc6f160751 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.CharsetEncoder; 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 d923c34f7c..09cc6c7ddd 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.MutableInstant; 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 a60d6e4c9c..0000000000 --- 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 <a href="https://github.com/FasterXML/jackson-core">Jackson</a>. - */ -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<int[]> 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<char[]> 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 435dfde08d..9c830453bf 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,18 @@ package org.apache.logging.log4j.jctools; import static java.util.Objects.requireNonNull; -import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY; +import static org.apache.logging.log4j.kit.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.RecyclerKeys; +import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler; import org.jctools.queues.MpmcArrayQueue; /** @@ -51,11 +52,12 @@ public final class JCToolsRecyclerFactoryProvider implements RecyclerFactoryProv @Override public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) { requireNonNull(environment, "environment"); - final int capacity = environment.getIntegerProperty("Recycler.capacity", DEFAULT_CAPACITY); - if (capacity < 1) { + final Integer capacity = + environment.getProperty(RecyclerKeys.Recycler.class).capacity(); + if (capacity != null && capacity < 1) { throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity); } - return new JCToolsMpmcRecyclerFactory(capacity); + return new JCToolsMpmcRecyclerFactory(capacity != null ? capacity : DEFAULT_CAPACITY); } private static final class JCToolsMpmcRecyclerFactory implements RecyclerFactory { 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 51e9d9e272..e5d121db0f 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 static org.assertj.core.api.Assertions.assertThat; 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<Class<?>> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream() + final List<Class<?>> providerClasses = ServiceLoaderUtil.safeStream( + RecyclerFactoryProvider.class, + ServiceLoader.load( + RecyclerFactoryProvider.class, getClass().getClassLoader()), + StatusLogger.getLogger()) .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder)) .<Class<?>>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 45a2a9312c..3fbcfbd2bd 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.LoggingException; 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<int[]> 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<DefaultLogBuilder> recycler; private final Logger statusLogger; @@ -125,7 +128,9 @@ public abstract class AbstractLogger implements ExtendedLogger { 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 @@ public abstract class AbstractLogger implements ExtendedLogger { // 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 47052fd14d..21a1cf92b3 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.BridgeAware; 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-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java similarity index 50% copy from log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java copy to log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java index 51e9d9e272..74c33b6f6d 100644 --- a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java @@ -14,24 +14,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jctools; +package org.apache.logging.log4j.kit.message; -import static org.assertj.core.api.Assertions.assertThat; +import org.apache.logging.log4j.kit.recycler.Recycler; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory2; -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 org.junit.jupiter.api.Test; - -class JCToolsRecyclerFactoryProviderTest { +/** + * A message factory backed by a {@link Recycler}. + * <p> + * Messages acquired from this factory <strong>must</strong> be released using the {@link #recycle} method. + * </p> + */ +public interface RecyclingMessageFactory extends MessageFactory2 { - @Test - void verify_is_the_first() { - final List<Class<?>> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream() - .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder)) - .<Class<?>>map(RecyclerFactoryProvider::getClass) - .toList(); - assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class); - } + /** + * Recycles a message back for potential reuse or cleanup. + * <p> + * + * </p> + * @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 0000000000..a8f822b176 --- /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 0000000000..99384388bb --- /dev/null +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.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; + +/** + * Contract for recycling strategies. + * This is the primary building block for logging components striving for garbage-free operation. + * + * @param <V> the recyclable type + * @since 3.0.0 + */ +public interface Recycler<V> { + + /** + * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting the number of available processors + */ + int DEFAULT_CAPACITY = Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8); + + /** + * 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-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java similarity index 50% copy from log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java copy to log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java index 51e9d9e272..aaaa6ce17a 100644 --- a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java @@ -14,24 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jctools; +package org.apache.logging.log4j.kit.recycler; -import static org.assertj.core.api.Assertions.assertThat; - -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 org.junit.jupiter.api.Test; - -class JCToolsRecyclerFactoryProviderTest { +/** + * Interface implemented by classes that need to interact with the {@link Recycler} that created them. + * + * @since 3.0.0 + */ +@FunctionalInterface +public interface RecyclerAware<V> { - @Test - void verify_is_the_first() { - final List<Class<?>> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream() - .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder)) - .<Class<?>>map(RecyclerFactoryProvider::getClass) - .toList(); - assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class); - } + void setRecycler(Recycler<V> 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 0000000000..e209ef6497 --- /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 <V> the recyclable type + * @return a new recycler + */ + default <V> Recycler<V> create(final Supplier<V> supplier) { + return create(supplier, ignored -> {}); + } + + /** + * Creates a new recycler using the given supplier and cleaner functions. + * <p> + * The provided supplier needs to make sure that generated instances are always clean. + * </p> + * <p> + * 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. + * </p> + * + * @param supplier a function to provide initial (and clean!) instances + * @param cleaner function to reset an instance before reuse + * @param <V> the recyclable type + * @return a new recycler + */ + <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> 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 0000000000..26ebd5900e --- /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. + * <p> + * 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. + * </p> + * + * @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/internal/ArrayQueue.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java new file mode 100644 index 0000000000..973bed589b --- /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 <E> the element type + */ +@InternalApi +@NotThreadSafe +final class ArrayQueue<E> extends AbstractQueue<E> { + + 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<E> 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 0000000000..8a58bbcac0 --- /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 <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) { + requireNonNull(supplier, "supplier"); + return new DummyRecycler<>(supplier); + } + + private static final class DummyRecycler<V> extends AbstractRecycler<V> { + + private DummyRecycler(final Supplier<V> supplier) { + super(supplier); + } + + @Override + public V acquire() { + return createInstance(); + } + + @Override + public void release(final V value) {} + } + } +} diff --git a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java similarity index 60% copy from log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java copy to log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java index 435dfde08d..fab04ed718 100644 --- a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java @@ -14,38 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jctools; +package org.apache.logging.log4j.kit.recycler.internal; import static java.util.Objects.requireNonNull; -import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY; +import static org.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY; 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.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.jctools.queues.MpmcArrayQueue; +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 implementation based on <a href="https://jctools.github.io/JCTools/">JCTools</a>. - * - * @since 3.0.0 + * A {@link Recycler} factory provider such that the recycler pools objects in a fixed-size queue. */ @ServiceProvider(RecyclerFactoryProvider.class) -public final class JCToolsRecyclerFactoryProvider implements RecyclerFactoryProvider { +public final class QueueingRecyclerFactoryProvider implements RecyclerFactoryProvider { @Override public int getOrder() { - return 600; + return 800; } @Override public String getName() { - return "jctools-mpmc"; + return "queue"; } @Override @@ -55,32 +53,36 @@ public final class JCToolsRecyclerFactoryProvider implements RecyclerFactoryProv if (capacity < 1) { throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity); } - return new JCToolsMpmcRecyclerFactory(capacity); + return new QueueingRecyclerFactory(capacity); } - private static final class JCToolsMpmcRecyclerFactory implements RecyclerFactory { + // Visible for testing + static final class QueueingRecyclerFactory implements RecyclerFactory { - private final int capacity; + // Visible for testing + final int capacity; - private JCToolsMpmcRecyclerFactory(final int capacity) { + private QueueingRecyclerFactory(int capacity) { this.capacity = capacity; } @Override - public <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner) { + public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) { requireNonNull(supplier, "supplier"); requireNonNull(cleaner, "cleaner"); - final MpmcArrayQueue<V> queue = new MpmcArrayQueue<>(capacity); - return new JCToolsMpmcRecycler<>(supplier, cleaner, queue); + final Queue<V> queue = new ArrayBlockingQueue<>(capacity); + return new QueueingRecycler<>(supplier, cleaner, queue); } - private static final class JCToolsMpmcRecycler<V> extends AbstractRecycler<V> { + // Visible for testing + static final class QueueingRecycler<V> extends AbstractRecycler<V> { private final Consumer<V> cleaner; - private final Queue<V> queue; + // Visible for testing + final Queue<V> queue; - private JCToolsMpmcRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final Queue<V> queue) { + private QueueingRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final Queue<V> queue) { super(supplier); this.cleaner = cleaner; this.queue = queue; 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 0000000000..2fc3d681bc --- /dev/null +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java @@ -0,0 +1,123 @@ +/* + * 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.kit.recycler.Recycler.DEFAULT_CAPACITY; +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.RecyclerKeys; +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}. + * <p> + * 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}. + * </p> + * + * @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 Integer capacity = + environment.getProperty(RecyclerKeys.Recycler.class).capacity(); + if (capacity != null && capacity < 1) { + throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity); + } + return new ThreadLocalRecyclerFactory(capacity != null ? capacity : DEFAULT_CAPACITY); + } + + // Visible for testing + static final class ThreadLocalRecyclerFactory implements RecyclerFactory { + + /** + * Maximum number of objects retained per thread. + * <p> + * 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. + * </p> + */ + // Visible for testing + final int capacity; + + private ThreadLocalRecyclerFactory(int capacity) { + this.capacity = capacity; + } + + @Override + public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) { + requireNonNull(supplier, "supplier"); + requireNonNull(cleaner, "cleaner"); + return new ThreadLocalRecycler<>(supplier, cleaner, capacity); + } + + // Visible for testing + static final class ThreadLocalRecycler<V> extends AbstractRecycler<V> { + + private final Consumer<V> cleaner; + + // Visible for testing + final ThreadLocal<Queue<V>> queueRef; + + private ThreadLocalRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final int capacity) { + super(supplier); + this.queueRef = ThreadLocal.withInitial(() -> new ArrayQueue<>(capacity)); + this.cleaner = cleaner; + } + + @Override + public V acquire() { + final Queue<V> 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<V> 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 0000000000..8d28b85cb9 --- /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 0000000000..764d7ec8d7 --- /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 <V> The type of recycled object. + * @since 3.0.0 + */ +public abstract class AbstractRecycler<V> implements Recycler<V> { + + private final Supplier<V> supplier; + + protected AbstractRecycler(final Supplier<V> supplier) { + this.supplier = requireNonNull(supplier, "supplier"); + } + + protected final V createInstance() { + final V instance = supplier.get(); + if (instance instanceof RecyclerAware) { + @SuppressWarnings("unchecked") + final RecyclerAware<V> recyclerAware = (RecyclerAware<V>) 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 0000000000..215ceeb792 --- /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-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java similarity index 51% copy from log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java copy to log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java index 51e9d9e272..7c1c63be58 100644 --- a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java +++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java @@ -14,24 +14,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jctools; +package org.apache.logging.log4j.kit.env; -import static org.assertj.core.api.Assertions.assertThat; +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; -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 org.junit.jupiter.api.Test; +public class TestPropertyEnvironment extends BasicPropertyEnvironment { -class JCToolsRecyclerFactoryProviderTest { + private final Map<String, String> props; - @Test - void verify_is_the_first() { - final List<Class<?>> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream() - .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder)) - .<Class<?>>map(RecyclerFactoryProvider::getClass) - .toList(); - assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class); + public TestPropertyEnvironment(final Map<String, String> props) { + this(props, StatusLogger.getLogger()); + } + + public TestPropertyEnvironment(final Map<String, String> 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 0739532e18..72b228ed21 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 @@ -28,12 +28,11 @@ import java.util.List; import java.util.Map; 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; @@ -222,23 +221,4 @@ class BasicPropertyEnvironmentTest { assertThat(actual).isEqualTo(expected); assertThat(logger.getMessages()).isEmpty(); } - - private static class TestPropertyEnvironment extends BasicPropertyEnvironment { - - private final Map<String, String> props; - - public TestPropertyEnvironment(final Map<String, String> props) { - this(props, StatusLogger.getLogger()); - } - - public TestPropertyEnvironment(final Map<String, String> 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 acf6f3689d..9595c9bfa2 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.Collections; 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 0000000000..40e6ac557f --- /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<String> 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<String> queueRef = new ArrayBlockingQueue<>(capacity); + final Queue<String> 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 0000000000..745e626395 --- /dev/null +++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java @@ -0,0 +1,84 @@ +/* + * 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.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY; +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 { + + @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 0000000000..19bcb48457 --- /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<String, String> 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 0000000000..f33afb1040 --- /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<RecyclableObject> recycler; + + private Queue<RecyclableObject> recyclerQueue; + + @BeforeEach + void setUp() { + final RecyclerFactory recyclerFactory = RecyclerFactoryTestUtil.createForEnvironment("threadLocal", CAPACITY); + assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class); + assert recyclerFactory != null; + recycler = (ThreadLocalRecycler<RecyclableObject>) 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<RecyclableObject> 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<RecyclableObject> 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<RecyclableObject> 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 18b23d96c3..b9f3fdd198 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.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.layout.ByteBufferDestination; import org.apache.logging.log4j.core.layout.Encoder; import org.apache.logging.log4j.core.layout.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.Plugin; 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 77a80af09c..4ce28da6a7 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.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.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 0f7fc723cc..cda477d215 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 64356bfe2e..603692bf7c 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.function.Function; 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 8d505eff8f..dddf66f727 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.function.Supplier; 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 c66b5c946a..0000000000 --- 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
