This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch feature/provider-revamp in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 3577d1d365e9c0fd9b5ebe13d0142db7bc2fabb4 Author: Piotr P. Karwasz <piotr.git...@karwasz.org> AuthorDate: Wed Mar 13 10:27:45 2024 +0100 Centralize initialization in `Provider` class We move the code responsible for the instantiation of `LoggerContextFactory` and `ThreadContextMap` from the static entry points to the logging system (`LogManager` and `ThreadContext`) to the `Provider` class. The `Provider` class is instantiated using `ServiceLoader`, so `log4j-core` 2.x and 3.x can reimplement the initialization process according to their own rules. E.g. `log4j-core` 3.x can use the DI to create an instance of `LoggerContextFactory` and `ThreadContextMap`. The following modification were performed: * a **new** system property `log4j.provider` was introduced, * the old `log4j2.loggerContextFactory` has been deprecated and revised: if set it selects the first provider that uses the given `LoggerContextFactory`. Therefore it selects now both the context factory and thread context map implementations, * private static configuration values were removed from `ThreadContextMap` implementations, helping test parallelisation, * a distinct `NoOpThreadContextStack` implementation has been introduced. --- .../org/apache/logging/log4j/TestProvider.java | 2 +- .../log4j/spi/DefaultThreadContextMapTest.java | 25 -- .../logging/log4j/spi/ThreadContextMapTest.java | 76 +++++ .../logging/log4j/util/ProviderUtilTest.java | 71 ++-- .../services/org.apache.logging.log4j.spi.Provider | 3 +- .../java/org/apache/logging/log4j/LogManager.java | 69 +--- .../org/apache/logging/log4j/ThreadContext.java | 90 +++-- .../CopyOnWriteSortedArrayThreadContextMap.java | 46 +-- .../logging/log4j/spi/DefaultThreadContextMap.java | 46 +-- .../log4j/spi/DefaultThreadContextStack.java | 8 + .../GarbageFreeSortedArrayThreadContextMap.java | 41 +-- .../org/apache/logging/log4j/spi/Provider.java | 365 ++++++++++++++++++--- .../logging/log4j/spi/ThreadContextMapFactory.java | 93 +----- .../apache/logging/log4j/util/ProviderUtil.java | 169 ++++++++-- .../apache/logging/log4j/util/package-info.java | 2 +- .../logging/log4j/core/impl/Log4jProvider.java | 2 +- 16 files changed, 676 insertions(+), 432 deletions(-) diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java index 3296647a6c..5feb4a1f25 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java @@ -24,6 +24,6 @@ import org.apache.logging.log4j.test.TestLoggerContextFactory; */ public class TestProvider extends Provider { public TestProvider() { - super(0, "2.6.0", TestLoggerContextFactory.class); + super(10, "2.6.0", TestLoggerContextFactory.class); } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java index a0ca976d73..dedc5be86b 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java @@ -24,11 +24,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.test.junit.InitializesThreadContext; -import org.apache.logging.log4j.test.junit.SetTestProperty; import org.apache.logging.log4j.test.junit.UsingThreadContextMap; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.ClearSystemProperty; /** * Tests the {@code DefaultThreadContextMap} class. @@ -217,26 +214,4 @@ public class DefaultThreadContextMapTest { map.put("key2", "value2"); assertEquals("{key2=value2}", map.toString()); } - - @Test - @ClearSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP) - @InitializesThreadContext - public void testThreadLocalNotInheritableByDefault() { - ThreadContextMapFactory.init(); - final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true); - assertFalse(threadLocal instanceof InheritableThreadLocal<?>); - } - - @Test - @SetTestProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true") - @InitializesThreadContext - public void testThreadLocalInheritableIfConfigured() { - ThreadContextMapFactory.init(); - try { - final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true); - assertTrue(threadLocal instanceof InheritableThreadLocal<?>); - } finally { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - } - } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java new file mode 100644 index 0000000000..42df896f9e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.spi; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Stream; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class ThreadContextMapTest { + + static Stream<ThreadContextMap> defaultMaps() { + return Stream.of( + new DefaultThreadContextMap(), + new CopyOnWriteSortedArrayThreadContextMap(), + new GarbageFreeSortedArrayThreadContextMap()); + } + + static Stream<ThreadContextMap> inheritableMaps() { + final Properties props = new Properties(); + props.setProperty("log4j2.isThreadContextMapInheritable", "true"); + final PropertiesUtil util = new PropertiesUtil(props); + return Stream.of( + new DefaultThreadContextMap(true, util), + new CopyOnWriteSortedArrayThreadContextMap(util), + new GarbageFreeSortedArrayThreadContextMap(util)); + } + + @ParameterizedTest + @MethodSource("defaultMaps") + void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) { + contextMap.put("key", "threadLocalNotInheritableByDefault"); + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + assertThat(executorService.submit(() -> contextMap.get("key"))) + .succeedsWithin(Duration.ofSeconds(1)) + .isEqualTo(null); + } finally { + executorService.shutdown(); + } + } + + @ParameterizedTest + @MethodSource("inheritableMaps") + void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) { + contextMap.put("key", "threadLocalInheritableIfConfigured"); + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + assertThat(executorService.submit(() -> contextMap.get("key"))) + .succeedsWithin(Duration.ofSeconds(1)) + .isEqualTo("threadLocalInheritableIfConfigured"); + } finally { + executorService.shutdown(); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java index fbba1ccacc..c9b59eb813 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java @@ -16,36 +16,61 @@ */ package org.apache.logging.log4j.util; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.net.URL; -import java.net.URLClassLoader; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.spi.LoggerContext; -import org.apache.logging.log4j.test.TestLoggerContext; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; +import org.apache.logging.log4j.TestProvider; +import org.apache.logging.log4j.test.TestLoggerContextFactory; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.CONCURRENT) +class ProviderUtilTest { -public class ProviderUtilTest { + /* + * Force initialization of ProviderUtil#PROVIDERS + */ + static { + ProviderUtil.lazyInit(); + } + + @Test + void should_select_provider_with_highest_priority() { + final PropertiesUtil properties = new PropertiesUtil(new Properties()); + assertThat(ProviderUtil.selectProvider(properties)) + .as("check selected provider") + .isInstanceOf(TestProvider.class); + } + + @Test + void should_recognize_log4j_provider_property() { + final Properties map = new Properties(); + map.setProperty("log4j.provider", LocalProvider.class.getName()); + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties)) + .as("check selected provider") + .isInstanceOf(LocalProvider.class); + } @Test - public void complexTest() throws Exception { - final File file = new File("target/classes"); - final ClassLoader classLoader = - new URLClassLoader(new URL[] {file.toURI().toURL()}); - final Worker worker = new Worker(); - worker.setContextClassLoader(classLoader); - worker.start(); - worker.join(); - assertTrue(worker.context instanceof TestLoggerContext, "Incorrect LoggerContext"); + void should_recognize_log4j_factory_property() { + final Properties map = new Properties(); + map.setProperty("log4j2.loggerContextFactory", LocalLoggerContextFactory.class.getName()); + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties).getLoggerContextFactory()) + .as("check selected logger context factory") + .isInstanceOf(LocalLoggerContextFactory.class); } - private static class Worker extends Thread { - LoggerContext context = null; + public static class LocalLoggerContextFactory extends TestLoggerContextFactory {} - @Override - public void run() { - context = LogManager.getContext(false); + /** + * A provider with a smaller priority than {@link org.apache.logging.log4j.TestProvider}. + */ + public static class LocalProvider extends org.apache.logging.log4j.spi.Provider { + public LocalProvider() { + super(0, CURRENT_VERSION, LocalLoggerContextFactory.class); } } } diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider index 5ae649a3f9..280fd54e8c 100644 --- a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider @@ -1 +1,2 @@ -org.apache.logging.log4j.TestProvider \ No newline at end of file +org.apache.logging.log4j.TestProvider +org.apache.logging.log4j.util.ProviderUtilTest.LocalProvider \ No newline at end of file diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java index 02e03235d9..d7def7ac7e 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java @@ -17,20 +17,14 @@ package org.apache.logging.log4j; import java.net.URI; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; import org.apache.logging.log4j.internal.LogManagerStatus; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.spi.LoggerContextFactory; -import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.spi.Terminable; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ProviderUtil; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; @@ -48,8 +42,10 @@ public class LogManager { /** * Log4j property to set to the fully qualified class name of a custom implementation of - * {@link org.apache.logging.log4j.spi.LoggerContextFactory}. + * {@link LoggerContextFactory}. + * @deprecated Replaced since 2.24.0 with {@value org.apache.logging.log4j.spi.Provider#PROVIDER_PROPERTY_NAME}. */ + @Deprecated public static final String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory"; /** @@ -62,69 +58,14 @@ public class LogManager { // for convenience private static final String FQCN = LogManager.class.getName(); - private static volatile LoggerContextFactory factory; + private static volatile LoggerContextFactory factory = + ProviderUtil.getProvider().getLoggerContextFactory(); /* * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be * extended to allow multiple implementations to be used. */ static { - // Shortcut binding to force a specific logging implementation. - final PropertiesUtil managerProps = PropertiesUtil.getProperties(); - final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); - if (factoryClassName != null) { - try { - factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); - } catch (final ClassNotFoundException cnfe) { - LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName); - } catch (final Exception ex) { - LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex); - } - } - - if (factory == null) { - final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>(); - // note that the following initial call to ProviderUtil may block until a Provider has been installed when - // running in an OSGi environment - if (ProviderUtil.hasProviders()) { - for (final Provider provider : ProviderUtil.getProviders()) { - final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory(); - if (factoryClass != null) { - try { - factories.put(provider.getPriority(), LoaderUtil.newInstanceOf(factoryClass)); - } catch (final Exception e) { - LOGGER.error( - "Unable to create class {} specified in provider URL {}", - factoryClass.getName(), - provider.getUrl(), - e); - } - } - } - - if (factories.isEmpty()) { - LOGGER.error( - "Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console..."); - factory = SimpleLoggerContextFactory.INSTANCE; - } else if (factories.size() == 1) { - factory = factories.get(factories.lastKey()); - } else { - final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n"); - for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { - sb.append("Factory: ") - .append(entry.getValue().getClass().getName()); - sb.append(", Weighting: ").append(entry.getKey()).append('\n'); - } - factory = factories.get(factories.lastKey()); - sb.append("Using factory: ").append(factory.getClass().getName()); - LOGGER.warn(sb.toString()); - } - } else { - LOGGER.error( - "Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console..."); - factory = SimpleLoggerContextFactory.INSTANCE; - } - } LogManagerStatus.setInitialized(true); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java index 02806b59d5..ddc36de030 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java @@ -23,19 +23,19 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import org.apache.logging.log4j.internal.map.StringArrayThreadContextMap; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.spi.CleanableThreadContextMap; import org.apache.logging.log4j.spi.DefaultThreadContextMap; import org.apache.logging.log4j.spi.DefaultThreadContextStack; -import org.apache.logging.log4j.spi.NoOpThreadContextMap; +import org.apache.logging.log4j.spi.MutableThreadContextStack; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.apache.logging.log4j.spi.ThreadContextMap; import org.apache.logging.log4j.spi.ThreadContextMap2; import org.apache.logging.log4j.spi.ThreadContextMapFactory; import org.apache.logging.log4j.spi.ThreadContextStack; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ProviderUtil; /** * The ThreadContext allows applications to store information either in a Map or a Stack. @@ -55,8 +55,6 @@ public final class ThreadContext { private static final long serialVersionUID = 1L; - private static final Iterator<String> EMPTY_ITERATOR = new EmptyIterator<>(); - @Override public String pop() { return null; @@ -101,42 +99,45 @@ public final class ThreadContext { @Override public ContextStack copy() { - return this; + return new MutableThreadContextStack(); } @Override - public <T> T[] toArray(final T[] a) { + public <T> T[] toArray(final T[] ignored) { throw new UnsupportedOperationException(); } @Override - public boolean add(final String e) { + public boolean add(final String ignored) { throw new UnsupportedOperationException(); } @Override - public boolean containsAll(final Collection<?> c) { + public void clear() {} + + @Override + public boolean containsAll(final Collection<?> ignored) { return false; } @Override - public boolean addAll(final Collection<? extends String> c) { + public boolean addAll(final Collection<? extends String> ignored) { throw new UnsupportedOperationException(); } @Override - public boolean removeAll(final Collection<?> c) { + public boolean removeAll(final Collection<?> ignored) { throw new UnsupportedOperationException(); } @Override - public boolean retainAll(final Collection<?> c) { + public boolean retainAll(final Collection<?> ignored) { throw new UnsupportedOperationException(); } @Override public Iterator<String> iterator() { - return EMPTY_ITERATOR; + return Collections.emptyIterator(); } @Override @@ -150,26 +151,34 @@ public final class ThreadContext { } } - /** - * An empty iterator. Since Java 1.7 added the Collections.emptyIterator() method, we have to make do. - * - * @param <E> the type of the empty iterator - */ - private static class EmptyIterator<E> implements Iterator<E> { + private static final class NoOpThreadContextStack extends EmptyThreadContextStack { + + @Override + public boolean add(final String ignored) { + return false; + } + + @Override + public boolean addAll(final Collection<? extends String> ignored) { + return false; + } + + @Override + public void push(final String ignored) {} @Override - public boolean hasNext() { + public boolean remove(final Object ignored) { return false; } @Override - public E next() { - throw new NoSuchElementException("This is an empty iterator!"); + public boolean removeAll(final Collection<?> ignored) { + return false; } @Override - public void remove() { - // no-op + public boolean retainAll(final Collection<?> ignored) { + return false; } } @@ -188,13 +197,12 @@ public final class ThreadContext { @SuppressWarnings("PublicStaticCollectionField") public static final ThreadContextStack EMPTY_STACK = new EmptyThreadContextStack(); - private static final String DISABLE_MAP = "disableThreadContextMap"; private static final String DISABLE_STACK = "disableThreadContextStack"; private static final String DISABLE_ALL = "disableThreadContext"; - private static boolean useStack; - private static ThreadContextMap contextMap; private static ThreadContextStack contextStack; + + private static ThreadContextMap contextMap; private static ReadOnlyThreadContextMap readOnlyContextMap; static { @@ -209,24 +217,16 @@ public final class ThreadContext { * <em>Consider private, used for testing.</em> */ public static void init() { + final PropertiesUtil properties = PropertiesUtil.getProperties(); + contextStack = properties.getBooleanProperty(DISABLE_STACK) || properties.getBooleanProperty(DISABLE_ALL) + ? new NoOpThreadContextStack() + : new DefaultThreadContextStack(); + // TODO: Fix the tests that need to reset the thread context map to use separate instance of the + // provider instead. ThreadContextMapFactory.init(); - contextMap = null; - final PropertiesUtil managerProps = PropertiesUtil.getProperties(); - final boolean disableAll = managerProps.getBooleanProperty(DISABLE_ALL); - useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || disableAll); - final boolean useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || disableAll); - - contextStack = new DefaultThreadContextStack(useStack); - if (!useMap) { - contextMap = new NoOpThreadContextMap(); - } else { - contextMap = ThreadContextMapFactory.createThreadContextMap(); - } - if (contextMap instanceof ReadOnlyThreadContextMap) { - readOnlyContextMap = (ReadOnlyThreadContextMap) contextMap; - } else { - readOnlyContextMap = null; - } + contextMap = ProviderUtil.getProvider().getThreadContextMapInstance(); + readOnlyContextMap = + contextMap instanceof ReadOnlyThreadContextMap ? (ReadOnlyThreadContextMap) contextMap : null; } /** @@ -385,8 +385,6 @@ public final class ThreadContext { * @return the internal data structure used to store thread context key-value pairs or {@code null} * @see ThreadContextMapFactory * @see DefaultThreadContextMap - * @see org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap - * @see org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap * @since 2.8 */ public static ReadOnlyThreadContextMap getThreadContextMap() { @@ -434,7 +432,7 @@ public final class ThreadContext { * @param stack The stack to use. */ public static void setStack(final Collection<String> stack) { - if (stack.isEmpty() || !useStack) { + if (stack.isEmpty()) { return; } contextStack.clear(); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java index 490507c8fc..ca9f501c21 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java @@ -54,48 +54,32 @@ class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1); - private static volatile int initialCapacity; - private static volatile boolean inheritableMap; - - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - static void init() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); - inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP); - } - static { EMPTY_CONTEXT_DATA.freeze(); - init(); } + private final int initialCapacity; private final ThreadLocal<StringMap> localMap; public CopyOnWriteSortedArrayThreadContextMap() { - this.localMap = createThreadLocalMap(); + this(PropertiesUtil.getProperties()); } - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - private ThreadLocal<StringMap> createThreadLocalMap() { - if (inheritableMap) { - return new InheritableThreadLocal<StringMap>() { - @Override - protected StringMap childValue(final StringMap parentValue) { - if (parentValue == null) { - return null; + CopyOnWriteSortedArrayThreadContextMap(final PropertiesUtil properties) { + initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); + localMap = properties.getBooleanProperty(INHERITABLE_MAP) + ? new InheritableThreadLocal<StringMap>() { + @Override + protected StringMap childValue(final StringMap parentValue) { + if (parentValue == null) { + return null; + } + final StringMap stringMap = createStringMap(parentValue); + stringMap.freeze(); + return stringMap; } - final StringMap stringMap = createStringMap(parentValue); - stringMap.freeze(); - return stringMap; } - }; - } - // if not inheritable, return plain ThreadLocal with null as initial value - return new ThreadLocal<>(); + : new ThreadLocal<StringMap>(); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java index 3c45eee98f..b4e0a48bef 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java @@ -43,40 +43,30 @@ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyString private final boolean useMap; private final ThreadLocal<Map<String, String>> localMap; - private static boolean inheritableMap; - - static { - init(); - } - - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) { - if (inheritableMap) { - return new InheritableThreadLocal<Map<String, String>>() { - @Override - protected Map<String, String> childValue(final Map<String, String> parentValue) { - return parentValue != null && isMapEnabled // - ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // - : null; - } - }; - } - // if not inheritable, return plain ThreadLocal with null as initial value - return new ThreadLocal<>(); - } - - static void init() { - inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP); - } - public DefaultThreadContextMap() { this(true); } + /** + * @deprecated Since 2.24.0 use the default constructor or {@link NoOpThreadContextMap} instead. + */ + @Deprecated public DefaultThreadContextMap(final boolean useMap) { + this(useMap, PropertiesUtil.getProperties()); + } + + DefaultThreadContextMap(final boolean useMap, final PropertiesUtil properties) { this.useMap = useMap; - this.localMap = createThreadLocalMap(useMap); + localMap = properties.getBooleanProperty(INHERITABLE_MAP) + ? new InheritableThreadLocal<Map<String, String>>() { + @Override + protected Map<String, String> childValue(final Map<String, String> parentValue) { + return parentValue != null && useMap + ? Collections.unmodifiableMap(new HashMap<>(parentValue)) + : null; + } + } + : new ThreadLocal<Map<String, String>>(); } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java index 1bf201e162..5ed3fb41ea 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java @@ -39,6 +39,14 @@ public class DefaultThreadContextStack implements ThreadContextStack, StringBuil private final boolean useStack; + public DefaultThreadContextStack() { + this(true); + } + + /** + * @deprecated since 2.24.0 without a replacement. + */ + @Deprecated public DefaultThreadContextStack(final boolean useStack) { this.useStack = useStack; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java index 0262be3b50..1622a0c455 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java @@ -52,42 +52,23 @@ class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap */ protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity"; + private final int initialCapacity; protected final ThreadLocal<StringMap> localMap; - private static volatile int initialCapacity; - private static volatile boolean inheritableMap; - - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - static void init() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); - inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP); - } - - static { - init(); - } - public GarbageFreeSortedArrayThreadContextMap() { - this.localMap = createThreadLocalMap(); + this(PropertiesUtil.getProperties()); } - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - private ThreadLocal<StringMap> createThreadLocalMap() { - if (inheritableMap) { - return new InheritableThreadLocal<StringMap>() { - @Override - protected StringMap childValue(final StringMap parentValue) { - return parentValue != null ? createStringMap(parentValue) : null; + GarbageFreeSortedArrayThreadContextMap(final PropertiesUtil properties) { + initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); + localMap = properties.getBooleanProperty(INHERITABLE_MAP) + ? new InheritableThreadLocal<StringMap>() { + @Override + protected StringMap childValue(final StringMap parentValue) { + return parentValue != null ? createStringMap(parentValue) : null; + } } - }; - } - // if not inheritable, return plain ThreadLocal with null as initial value - return new ThreadLocal<>(); + : new ThreadLocal<>(); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java index a67ccad231..c1b5e14278 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java @@ -20,39 +20,135 @@ import java.lang.ref.WeakReference; import java.net.URL; import java.util.Properties; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; /** - * Model class for a Log4j 2 provider. The properties in this class correspond to the properties used in a - * {@code META-INF/log4j-provider.properties} file. Note that this class is automatically created by Log4j and should - * not be used by providers. + * Service class used to bind the Log4j API with an implementation. + * <p> + * Implementors should register an implementation of this class with {@link java.util.ServiceLoader}. + * </p> + * <p> + * <strong>Deprecated:</strong> the automatic registration of providers from + * {@code META-INF/log4j-provider.properties} is supported for compatibility reasons. Support for this file will + * be dropped in a future version. + * </p> */ public class Provider { + protected static final String CURRENT_VERSION = "2.6.0"; + /** * Property name to set for a Log4j 2 provider to specify the priority of this implementation. + * @deprecated since 2.24.0 */ + @Deprecated public static final String FACTORY_PRIORITY = "FactoryPriority"; + /** - * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.ThreadContextMap}. + * Property name to set to the implementation of {@link ThreadContextMap}. + * @deprecated since 2.24.0 */ + @Deprecated public static final String THREAD_CONTEXT_MAP = "ThreadContextMap"; + /** - * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.LoggerContextFactory}. + * Property name to set to the implementation of {@link LoggerContextFactory}. + * @deprecated since 2.24.0 */ + @Deprecated public static final String LOGGER_CONTEXT_FACTORY = "LoggerContextFactory"; - private static final Integer DEFAULT_PRIORITY = Integer.valueOf(-1); + /** + * System property used to specify the class name of the provider to use. + * @since 2.24.0 + */ + public static final String PROVIDER_PROPERTY_NAME = "log4j.provider"; + + // Bundled context map implementations + private static final String BASE = "org.apache.logging.log4j.internal.map."; + + /** + * Constant used to disable the {@link ThreadContextMap}. + * <p> + * <strong>Warning:</strong> the value of this constant does not point to a concrete class name. + * </p> + * @see #getThreadContextMap + */ + public static final String NO_OP_CONTEXT_MAP = BASE + "NoOp"; + + /** + * Constant used to select a web application-safe implementation of {@link ThreadContextMap}. + * <p> + * This implementation only binds JRE classes to {@link ThreadLocal} variables. + * </p> + * <p> + * <strong>Warning:</strong> the value of this constant does not point to a concrete class name. + * </p> + * @see #getThreadContextMap + */ + public static final String WEB_APP_CONTEXT_MAP = BASE + "WebApp"; + + /** + * Constant used to select a copy-on-write implementation of {@link ThreadContextMap}. + * <p> + * <strong>Warning:</strong> the value of this constant does not point to a concrete class name. + * </p> + * @see #getThreadContextMap + */ + public static final String COPY_ON_WRITE_CONTEXT_MAP = BASE + "CopyOnWrite"; + + /** + * Constant used to select a garbage-free implementation of {@link ThreadContextMap}. + * <p> + * This implementation must ensure that common operations don't create new object instances. The drawback is + * the necessity to bind custom classes to {@link ThreadLocal} variables. + * </p> + * <p> + * <strong>Warning:</strong> the value of this constant does not point to a concrete class name. + * </p> + * @see #getThreadContextMap + */ + public static final String GARBAGE_FREE_CONTEXT_MAP = BASE + "GarbageFree"; + + // Property keys relevant for context map selection + private static final String DISABLE_CONTEXT_MAP = "log4j2.disableThreadContextMap"; + private static final String DISABLE_THREAD_CONTEXT = "log4j2.disableThreadContext"; + private static final String THREAD_CONTEXT_MAP_PROPERTY = "log4j2.threadContextMap"; + private static final String GC_FREE_THREAD_CONTEXT_PROPERTY = "log4j2.garbagefree.threadContextMap"; + + private static final Integer DEFAULT_PRIORITY = -1; private static final Logger LOGGER = StatusLogger.getLogger(); private final Integer priority; + // LoggerContextFactory + @Deprecated private final String className; + private final Class<? extends LoggerContextFactory> loggerContextFactoryClass; + private final Lazy<LoggerContextFactory> loggerContextFactoryLazy = Lazy.lazy(this::createLoggerContextFactory); + // ThreadContextMap + @Deprecated private final String threadContextMap; + private final Class<? extends ThreadContextMap> threadContextMapClass; + private final Lazy<ThreadContextMap> threadContextMapLazy = Lazy.lazy(this::createThreadContextMap); private final String versions; + + @Deprecated private final URL url; + + @Deprecated private final WeakReference<ClassLoader> classLoader; + /** + * Constructor used by the deprecated {@code META-INF/log4j-provider.properties} format. + * @deprecated since 2.24.0 + */ + @Deprecated public Provider(final Properties props, final URL url, final ClassLoader classLoader) { this.url = url; this.classLoader = new WeakReference<>(classLoader); @@ -65,6 +161,21 @@ public class Provider { versions = null; } + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}. + * @since 2.24.0 + */ + public Provider(final Integer priority, final String versions) { + this(priority, versions, null, null); + } + + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}, + * @param loggerContextFactoryClass A public exported implementation of {@link LoggerContextFactory} or {@code + * null} if {@link #createLoggerContextFactory()} is also implemented. + */ public Provider( final Integer priority, final String versions, @@ -72,19 +183,28 @@ public class Provider { this(priority, versions, loggerContextFactoryClass, null); } + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}, + * @param loggerContextFactoryClass A public exported implementation of {@link LoggerContextFactory} or {@code + * null} if {@link #createLoggerContextFactory()} is also implemented, + * @param threadContextMapClass A public exported implementation of {@link ThreadContextMap} or {@code null} if + * {@link #createThreadContextMap()} is implemented. + */ public Provider( final Integer priority, final String versions, final Class<? extends LoggerContextFactory> loggerContextFactoryClass, final Class<? extends ThreadContextMap> threadContextMapClass) { - this.url = null; - this.classLoader = null; - this.priority = priority; + this.priority = priority != null ? priority : DEFAULT_PRIORITY; + this.versions = versions; this.loggerContextFactoryClass = loggerContextFactoryClass; this.threadContextMapClass = threadContextMapClass; - this.className = null; - this.threadContextMap = null; - this.versions = versions; + // Deprecated + className = null; + threadContextMap = null; + url = null; + classLoader = new WeakReference<>(null); } /** @@ -97,7 +217,9 @@ public class Provider { /** * Gets the priority (natural ordering) of this Provider. - * + * <p> + * Log4j selects the highest priority provider. + * </p> * @return the priority of this Provider */ public Integer getPriority() { @@ -105,119 +227,254 @@ public class Provider { } /** - * Gets the class name of the {@link org.apache.logging.log4j.spi.LoggerContextFactory} implementation of this - * Provider. + * Gets the class name of the {@link LoggerContextFactory} implementation of this Provider. * - * @return the class name of a LoggerContextFactory implementation + * @return the class name of a LoggerContextFactory implementation or {@code null} if unspecified. + * @see #loadLoggerContextFactory() */ public String getClassName() { - if (loggerContextFactoryClass != null) { - return loggerContextFactoryClass.getName(); - } - return className; + return loggerContextFactoryClass != null ? loggerContextFactoryClass.getName() : className; } /** - * Loads the {@link org.apache.logging.log4j.spi.LoggerContextFactory} class specified by this Provider. + * Loads the {@link LoggerContextFactory} class specified by this Provider. * - * @return the LoggerContextFactory implementation class or {@code null} if there was an error loading it + * @return the LoggerContextFactory implementation class or {@code null} if unspecified or a loader error occurred. + * @see #createLoggerContextFactory() */ public Class<? extends LoggerContextFactory> loadLoggerContextFactory() { if (loggerContextFactoryClass != null) { return loggerContextFactoryClass; } - if (className == null) { - return null; - } + final String className = getClassName(); final ClassLoader loader = classLoader.get(); - if (loader == null) { + // Support for deprecated {@code META-INF/log4j-provider.properties} format. + // In the remaining cases {@code loader == null}. + if (loader == null || className == null) { return null; } try { final Class<?> clazz = loader.loadClass(className); if (LoggerContextFactory.class.isAssignableFrom(clazz)) { return clazz.asSubclass(LoggerContextFactory.class); + } else { + LOGGER.error( + "Class {} specified in {} does not extend {}", + className, + getUrl(), + LoggerContextFactory.class.getName()); } } catch (final Exception e) { - LOGGER.error("Unable to create class {} specified in {}", className, url.toString(), e); + LOGGER.error("Unable to create class {} specified in {}", className, getUrl(), e); } return null; } /** - * Gets the class name of the {@link org.apache.logging.log4j.spi.ThreadContextMap} implementation of this Provider. - * + * Extension point for providers to create a {@link LoggerContextFactory}. + * @since 2.24.0 + */ + protected LoggerContextFactory createLoggerContextFactory() { + final Class<? extends LoggerContextFactory> factoryClass = loadLoggerContextFactory(); + if (factoryClass != null) { + try { + return LoaderUtil.newInstanceOf(factoryClass); + } catch (final Exception e) { + LOGGER.error( + "Unable to create instance of class {} specified in {}", factoryClass.getName(), getUrl(), e); + } + } + LOGGER.warn("Falling back to {}", SimpleLoggerContextFactory.INSTANCE); + return SimpleLoggerContextFactory.INSTANCE; + } + + /** + * @return A lazily initialized logger context factory + * @since 2.24.0 + */ + public final LoggerContextFactory getLoggerContextFactory() { + return loggerContextFactoryLazy.get(); + } + + /** + * Gets the class name of the {@link ThreadContextMap} implementation of this Provider. + * <p> + * This method should return one of the internal implementations: + * <ol> + * <li>{@code null} if {@link #loadThreadContextMap} is implemented,</li> + * <li>{@link #NO_OP_CONTEXT_MAP},</li> + * <li>{@link #WEB_APP_CONTEXT_MAP},</li> + * <li>{@link #COPY_ON_WRITE_CONTEXT_MAP},</li> + * <li>{@link #GARBAGE_FREE_CONTEXT_MAP}.</li> + * </ol> + * </p> * @return the class name of a ThreadContextMap implementation + * @see #loadThreadContextMap() */ public String getThreadContextMap() { if (threadContextMapClass != null) { return threadContextMapClass.getName(); } - return threadContextMap; + // Field value + if (threadContextMap != null) { + return threadContextMap; + } + // Properties + final PropertiesUtil props = PropertiesUtil.getProperties(); + if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) { + return NO_OP_CONTEXT_MAP; + } + final String threadContextMapClass = props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY); + if (threadContextMapClass != null) { + return threadContextMapClass; + } + // Default based on properties + if (Constants.ENABLE_THREADLOCALS) { + return props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY) + ? GC_FREE_THREAD_CONTEXT_PROPERTY + : COPY_ON_WRITE_CONTEXT_MAP; + } + return WEB_APP_CONTEXT_MAP; } /** - * Loads the {@link org.apache.logging.log4j.spi.ThreadContextMap} class specified by this Provider. + * Loads the {@link ThreadContextMap} class specified by this Provider. * - * @return the ThreadContextMap implementation class or {@code null} if there was an error loading it + * @return the {@code ThreadContextMap} implementation class or {@code null} if unspecified or a loading error + * occurred. + * @see #createThreadContextMap() */ public Class<? extends ThreadContextMap> loadThreadContextMap() { if (threadContextMapClass != null) { return threadContextMapClass; } - if (threadContextMap == null) { - return null; - } + final String threadContextMap = getThreadContextMap(); final ClassLoader loader = classLoader.get(); - if (loader == null) { + // Support for deprecated {@code META-INF/log4j-provider.properties} format. + // In the remaining cases {@code loader == null}. + if (loader == null || threadContextMap == null) { return null; } try { final Class<?> clazz = loader.loadClass(threadContextMap); if (ThreadContextMap.class.isAssignableFrom(clazz)) { return clazz.asSubclass(ThreadContextMap.class); + } else { + LOGGER.error( + "Class {} specified in {} does not extend {}", + threadContextMap, + getUrl(), + ThreadContextMap.class.getName()); } } catch (final Exception e) { - LOGGER.error("Unable to create class {} specified in {}", threadContextMap, url.toString(), e); + LOGGER.error("Unable to load class {} specified in {}", threadContextMap, url.toString(), e); } return null; } + /** + * Extension point for providers to create a {@link ThreadContextMap} + * @implNote The default implementation: + * <ol> + * <li>calls {@link #loadThreadContextMap},</li> + * <li>if the previous call returns {@code null}, it calls {@link #getThreadContextMap} to instantiate one of + * the internal implementations,</li> + * <li>it returns a no-op map otherwise.</li> + * </ol> + * @since 2.24.0 + */ + protected ThreadContextMap createThreadContextMap() { + final Class<? extends ThreadContextMap> threadContextMapClass = loadThreadContextMap(); + if (threadContextMapClass != null) { + try { + return LoaderUtil.newInstanceOf(threadContextMapClass); + } catch (final Exception e) { + LOGGER.error( + "Unable to create instance of class {} specified in {}", + threadContextMapClass.getName(), + getUrl(), + e); + } + } + // Standard Log4j API implementations are internal and can be only specified by name: + final String threadContextMap = getThreadContextMap(); + if (threadContextMap != null) { + /* + * The constructors are called explicitly to improve GraalVM support. + * + * The class names of the package-private implementations from version 2.23.1 must be recognized even + * if the class is moved. + */ + switch (threadContextMap) { + case NO_OP_CONTEXT_MAP: + case "org.apache.logging.log4j.spi.NoOpThreadContextMap": + return new NoOpThreadContextMap(); + case WEB_APP_CONTEXT_MAP: + case "org.apache.logging.log4j.spi.DefaultThreadContextMap": + return new DefaultThreadContextMap(); + case GARBAGE_FREE_CONTEXT_MAP: + case "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap": + return new GarbageFreeSortedArrayThreadContextMap(); + case COPY_ON_WRITE_CONTEXT_MAP: + case "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap": + return new CopyOnWriteSortedArrayThreadContextMap(); + } + } + LOGGER.warn("Falling back to {}", NoOpThreadContextMap.class.getName()); + return new NoOpThreadContextMap(); + } + + // Used for testing + void resetThreadContextMap() { + threadContextMapLazy.set(null); + } + + /** + * @return A lazily initialized thread context map. + * @since 2.24.0 + */ + public final ThreadContextMap getThreadContextMapInstance() { + return threadContextMapLazy.get(); + } + /** * Gets the URL containing this Provider's Log4j details. * - * @return the URL corresponding to the Provider {@code META-INF/log4j-provider.properties} file + * @return the URL corresponding to the Provider {@code META-INF/log4j-provider.properties} file or {@code null} + * for a provider class. + * @deprecated since 2.24.0, without replacement. */ + @Deprecated public URL getUrl() { return url; } @Override public String toString() { - final StringBuilder result = new StringBuilder("Provider["); + final StringBuilder result = + new StringBuilder("Provider '").append(getClass().getName()).append("'"); if (!DEFAULT_PRIORITY.equals(priority)) { - result.append("priority=").append(priority).append(", "); + result.append("\n\tpriority = ").append(priority); } + final String threadContextMap = getThreadContextMap(); if (threadContextMap != null) { - result.append("threadContextMap=").append(threadContextMap).append(", "); - } else if (threadContextMapClass != null) { - result.append("threadContextMapClass=").append(threadContextMapClass.getName()); + result.append("\n\tthreadContextMap = ").append(threadContextMap); } - if (className != null) { - result.append("className=").append(className).append(", "); - } else if (loggerContextFactoryClass != null) { - result.append("class=").append(loggerContextFactoryClass.getName()); + final String loggerContextFactory = getClassName(); + if (loggerContextFactory != null) { + result.append("\n\tloggerContextFactory = ").append(loggerContextFactory); } if (url != null) { - result.append("url=").append(url); + result.append("\n\turl = ").append(url); } - final ClassLoader loader; - if (classLoader == null || (loader = classLoader.get()) == null) { - result.append(", classLoader=null(not reachable)"); - } else { - result.append(", classLoader=").append(loader); + if (Provider.class.equals(getClass())) { + final ClassLoader loader = classLoader.get(); + if (loader == null) { + result.append("\n\tclassLoader = null or not reachable"); + } else { + result.append("\n\tclassLoader = ").append(loader); + } } - result.append("]"); return result.toString(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java index 566ae1463c..b00bd97985 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java @@ -16,13 +16,8 @@ */ package org.apache.logging.log4j.spi; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Constants; -import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ProviderUtil; /** @@ -47,102 +42,18 @@ import org.apache.logging.log4j.util.ProviderUtil; * @since 2.7 */ public final class ThreadContextMapFactory { - private static final Logger LOGGER = StatusLogger.getLogger(); - private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap"; - private static final String GC_FREE_THREAD_CONTEXT_KEY = "log4j2.garbagefree.threadContextMap"; - - private static boolean GcFreeThreadContextKey; - private static String ThreadContextMapName; - private static final String GARBAGE_FREE_CONTEXT_MAP = - "org.apache.logging.log4j.spi" + ".GarbageFreeSortedArrayThreadContextMap"; - private static final String COPY_ON_WRITE_CONTEXT_MAP = - "org.apache.logging.log4j.spi" + ".CopyOnWriteSortedArrayThreadContextMap"; - - static { - initPrivate(); - } /** * Initializes static variables based on system properties. Normally called when this class is initialized by the VM * and when Log4j is reconfigured. */ public static void init() { - CopyOnWriteSortedArrayThreadContextMap.init(); - GarbageFreeSortedArrayThreadContextMap.init(); - DefaultThreadContextMap.init(); - initPrivate(); - } - - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - private static void initPrivate() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - ThreadContextMapName = properties.getStringProperty(THREAD_CONTEXT_KEY); - GcFreeThreadContextKey = properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY); + ProviderUtil.getProvider().resetThreadContextMap(); } private ThreadContextMapFactory() {} public static ThreadContextMap createThreadContextMap() { - final ClassLoader cl = ProviderUtil.findClassLoader(); - ThreadContextMap result = null; - if (ThreadContextMapName != null) { - /* - * Two implementation are package-private classes, so we instantiate them directly. - * Other implementation must be publicly accessible (through `LoaderUtil`). - */ - switch (ThreadContextMapName) { - case GARBAGE_FREE_CONTEXT_MAP: - result = new GarbageFreeSortedArrayThreadContextMap(); - break; - case COPY_ON_WRITE_CONTEXT_MAP: - result = new CopyOnWriteSortedArrayThreadContextMap(); - break; - default: - try { - result = LoaderUtil.newCheckedInstanceOf(ThreadContextMapName, ThreadContextMap.class); - } catch (final ClassNotFoundException cnfe) { - LOGGER.error("Unable to locate configured ThreadContextMap {}", ThreadContextMapName); - } catch (final Exception ex) { - LOGGER.error("Unable to create configured ThreadContextMap {}", ThreadContextMapName, ex); - } - } - } - if (result == null && ProviderUtil.hasProviders() && LogManager.getFactory() != null) { // LOG4J2-1658 - final String factoryClassName = LogManager.getFactory().getClass().getName(); - for (final Provider provider : ProviderUtil.getProviders()) { - if (factoryClassName.equals(provider.getClassName())) { - final Class<? extends ThreadContextMap> clazz = provider.loadThreadContextMap(); - if (clazz != null) { - try { - result = LoaderUtil.newInstanceOf(clazz); - break; - } catch (final Exception e) { - LOGGER.error( - "Unable to locate or load configured ThreadContextMap {}", - provider.getThreadContextMap(), - e); - result = createDefaultThreadContextMap(); - } - } - } - } - } - if (result == null) { - result = createDefaultThreadContextMap(); - } - return result; - } - - private static ThreadContextMap createDefaultThreadContextMap() { - if (Constants.ENABLE_THREADLOCALS) { - if (GcFreeThreadContextKey) { - return new GarbageFreeSortedArrayThreadContextMap(); - } - return new CopyOnWriteSortedArrayThreadContextMap(); - } - return new DefaultThreadContextMap(true); + return ProviderUtil.getProvider().createThreadContextMap(); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java index eeba052486..ca7c4c7178 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java @@ -16,31 +16,40 @@ */ package org.apache.logging.log4j.util; +import static org.apache.logging.log4j.LogManager.FACTORY_PROPERTY_NAME; +import static org.apache.logging.log4j.spi.Provider.PROVIDER_PROPERTY_NAME; + import aQute.bnd.annotation.Cardinality; import aQute.bnd.annotation.Resolution; -import aQute.bnd.annotation.baseline.BaselineIgnore; import aQute.bnd.annotation.spi.ServiceConsumer; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.net.URL; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.Properties; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.NoOpThreadContextMap; import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.status.StatusLogger; /** - * <em>Consider this class private.</em> Utility class for Log4j {@link Provider}s. When integrating with an application - * container framework, any Log4j Providers not accessible through standard classpath scanning should - * {@link #loadProvider(java.net.URL, ClassLoader)} a classpath accordingly. + * <em>Consider this class private.</em> + * <p> + * Utility class for Log4j {@link Provider}s. When integrating with an application container framework, any Log4j + * Providers not accessible through standard classpath scanning should + * {@link #loadProvider(java.net.URL, ClassLoader)} a classpath accordingly. + * </p> */ @InternalApi -@BaselineIgnore("2.22.0") @ServiceConsumer(value = Provider.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE) public final class ProviderUtil { @@ -55,9 +64,11 @@ public final class ProviderUtil { static final Collection<Provider> PROVIDERS = new HashSet<>(); /** - * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support. - * - * @since 2.1 + * Guards the ProviderUtil singleton instance from lazy initialization. + * <p> + * This is primarily used for OSGi support. It allows the OSGi Activator to pause the startup and wait for a + * Provider to be installed. See <a href="https://issues.apache.org/jira/browse/LOG4J2-373">LOG4J2-373</a>. + * </p> */ static final Lock STARTUP_LOCK = new ReentrantLock(); @@ -65,19 +76,10 @@ public final class ProviderUtil { private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"}; private static final Logger LOGGER = StatusLogger.getLogger(); - // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and - // wait for a Provider to be installed. See LOG4J2-373 - private static volatile ProviderUtil instance; - - private ProviderUtil() { - ServiceLoaderUtil.loadServices(Provider.class, MethodHandles.lookup(), false) - .filter(provider -> validVersion(provider.getVersions())) - .forEach(PROVIDERS::add); + private static volatile Provider PROVIDER; + private static final Provider FALLBACK_PROVIDER = new SimpleProvider(); - for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) { - loadProvider(resource.getUrl(), resource.getClassLoader()); - } - } + private ProviderUtil() {} static void addProvider(final Provider provider) { PROVIDERS.add(provider); @@ -107,16 +109,6 @@ public final class ProviderUtil { } } - /** - * - * @param classLoader null can be used to mark the bootstrap class loader. - */ - static void loadProviders(final ClassLoader classLoader) { - ServiceLoaderUtil.loadClassloaderServices(Provider.class, MethodHandles.lookup(), classLoader, true) - .filter(provider -> validVersion(provider.getVersions())) - .forEach(PROVIDERS::add); - } - /** * @deprecated Use {@link #loadProvider(java.net.URL, ClassLoader)} instead. Will be removed in 3.0. */ @@ -129,6 +121,14 @@ public final class ProviderUtil { } } + /** + * @since 2.24.0 + */ + public static Provider getProvider() { + lazyInit(); + return PROVIDER; + } + public static Iterable<Provider> getProviders() { lazyInit(); return PROVIDERS; @@ -141,17 +141,26 @@ public final class ProviderUtil { /** * Lazily initializes the ProviderUtil singleton. - * - * @since 2.1 + * <p> + * Note that the following initial call to ProviderUtil may block until a Provider has been installed when + * running in an OSGi environment. + * </p> */ static void lazyInit() { - // noinspection DoubleCheckedLocking - if (instance == null) { + if (PROVIDER == null) { try { STARTUP_LOCK.lockInterruptibly(); try { - if (instance == null) { - instance = new ProviderUtil(); + if (PROVIDER == null) { + ServiceLoaderUtil.loadServices(Provider.class, MethodHandles.lookup(), false) + .filter(provider -> validVersion(provider.getVersions())) + .forEach(PROVIDERS::add); + + for (final LoaderUtil.UrlResource resource : + LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) { + loadProvider(resource.getUrl(), resource.getClassLoader()); + } + PROVIDER = selectProvider(PropertiesUtil.getProperties()); } } finally { STARTUP_LOCK.unlock(); @@ -163,6 +172,88 @@ public final class ProviderUtil { } } + /** + * Used to test the public {@link #getProvider()} method. + */ + static Provider selectProvider(final PropertiesUtil properties) { + Provider selected = null; + // 1. Select provider using "log4j.provider" property + final String providerClass = properties.getStringProperty(PROVIDER_PROPERTY_NAME); + if (providerClass != null) { + try { + selected = LoaderUtil.newInstanceOf(providerClass); + } catch (final Exception e) { + LOGGER.error("Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e); + } + } + // 2. Use deprecated "log4j2.loggerContextFactory" property to choose the provider + final String factoryClassName = properties.getStringProperty(FACTORY_PROPERTY_NAME); + if (factoryClassName != null) { + if (selected != null) { + LOGGER.warn( + "Ignoring {} system property, since {} was set.", + FACTORY_PROPERTY_NAME, + PROVIDER_PROPERTY_NAME); + // 2a. Scan the known providers for one matching the logger context factory class name. + } else { + LOGGER.warn( + "Usage of the {} property is deprecated. Use the {} property instead.", + FACTORY_PROPERTY_NAME, + PROVIDER_PROPERTY_NAME); + for (final Provider provider : PROVIDERS) { + if (factoryClassName.equals(provider.getClassName())) { + selected = provider; + break; + } + } + } + // 2b. Instantiate + if (selected == null) { + LOGGER.warn( + "No provider found using {} as logger context factory. The factory will be instantiated directly.", + factoryClassName); + try { + final Class<?> clazz = LoaderUtil.loadClass(factoryClassName); + if (LoggerContextFactory.class.isAssignableFrom(clazz)) { + selected = new Provider(null, Strings.EMPTY, clazz.asSubclass(LoggerContextFactory.class)); + } else { + LOGGER.error( + "Class {} specified in the {} system property does not extend {}", + factoryClassName, + FACTORY_PROPERTY_NAME, + LoggerContextFactory.class.getName()); + } + } catch (final Exception e) { + LOGGER.error( + "Unable to create class {} specified in the {} system property", + factoryClassName, + FACTORY_PROPERTY_NAME, + e); + } + } + } + // 3. Select a provider automatically. + if (selected == null) { + final Comparator<Provider> comparator = Comparator.comparing(Provider::getPriority); + switch (PROVIDERS.size()) { + case 0: + LOGGER.error("Log4j API could not find a logging provider."); + break; + case 1: + break; + default: + LOGGER.warn(PROVIDERS.stream() + .sorted(comparator) + .map(Provider::toString) + .collect(Collectors.joining("\n", "Log4j API found multiple logging providers:\n", ""))); + break; + } + selected = PROVIDERS.stream().max(comparator).orElse(FALLBACK_PROVIDER); + } + LOGGER.info("Using provider:\n{}", selected); + return selected; + } + public static ClassLoader findClassLoader() { return LoaderUtil.getThreadContextClassLoader(); } @@ -175,4 +266,10 @@ public final class ProviderUtil { } return false; } + + private static final class SimpleProvider extends Provider { + private SimpleProvider() { + super(null, CURRENT_VERSION, SimpleLoggerContextFactory.class, NoOpThreadContextMap.class); + } + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java index 8265e82149..757248862a 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java @@ -20,7 +20,7 @@ * There are no guarantees for binary or logical compatibility in this package. */ @Export -@Version("2.22.0") +@Version("2.24.0") package org.apache.logging.log4j.util; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java index 1c3652626a..bd0b62337c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java @@ -26,6 +26,6 @@ import org.apache.logging.log4j.spi.Provider; @ServiceProvider(value = Provider.class, resolution = Resolution.OPTIONAL) public class Log4jProvider extends Provider { public Log4jProvider() { - super(10, "2.6.0", Log4jContextFactory.class); + super(10, CURRENT_VERSION, Log4jContextFactory.class); } }