This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch ScopedContext-replace-with-interface in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 4490b4337e68d40c232bfe275f4eb3bbb01880b0 Author: Ralph Goers <[email protected]> AuthorDate: Wed Apr 3 08:19:52 2024 -0700 Move ContextDataProviders to the API --- .../org/apache/logging/log4j/test/TestLogger.java | 10 +- .../apache/logging/log4j/ResourceLoggerTest.java | 7 +- .../java/org/apache/logging/log4j/ContextData.java | 106 ++++++++ .../org/apache/logging/log4j/ResourceLogger.java | 5 +- .../org/apache/logging/log4j/ScopedContext.java | 62 ++++- .../apache/logging/log4j/simple/SimpleLogger.java | 7 +- .../logging/log4j/spi}/ContextDataProvider.java | 47 +++- .../ScopedContextDataProvider.java} | 41 ++- .../log4j/spi/ThreadContextDataProvider.java | 35 ++- .../apache/logging/log4j/ResourceLoggerTest.java | 3 +- .../core/impl/ThreadContextDataInjectorTest.java | 2 +- .../logging/log4j/core/ContextDataInjector.java | 16 +- .../logging/log4j/core/async/AsyncLogger.java | 10 +- .../log4j/core/async/RingBufferLogEvent.java | 4 +- .../core/async/RingBufferLogEventTranslator.java | 10 +- .../log4j/core/filter/DynamicThresholdFilter.java | 19 +- .../log4j/core/filter/ThreadContextMapFilter.java | 25 +- .../logging/log4j/core/filter/package-info.java | 2 +- .../core/impl/ContextDataInjectorFactory.java | 25 +- .../log4j/core/impl/JdkMapAdapterStringMap.java | 2 +- .../logging/log4j/core/impl/Log4jLogEvent.java | 21 ++ .../log4j/core/impl/ReusableLogEventFactory.java | 24 +- .../log4j/core/impl/ThreadContextDataInjector.java | 284 +++++++++++---------- .../log4j/core/lookup/ContextMapLookup.java | 18 +- .../logging/log4j/core/lookup/package-info.java | 2 +- .../apache/logging/log4j/core/osgi/Activator.java | 8 +- .../apache/logging/log4j/core/package-info.java | 2 +- .../log4j/core/util/ContextDataProvider.java | 14 +- .../logging/log4j/core/util/package-info.java | 2 +- 29 files changed, 548 insertions(+), 265 deletions(-) diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java index d90258e5a2..2387fee7e6 100644 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java @@ -23,10 +23,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ScopedContext; -import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.ParameterizedMapMessage; @@ -82,11 +81,8 @@ public class TestLogger extends AbstractLogger { sb.append(' '); } sb.append(message.getFormattedMessage()); - Map<String, ScopedContext.Renderable> contextMap = ScopedContext.getContextMap(); - final Map<String, String> mdc = new HashMap<>(ThreadContext.getImmutableContext()); - if (contextMap != null && !contextMap.isEmpty()) { - contextMap.forEach((key, value) -> mdc.put(key, value.render())); - } + final Map<String, String> mdc = new HashMap<>(ContextData.size()); + ContextData.addAll(mdc); if (!mdc.isEmpty()) { sb.append(' '); sb.append(mdc); diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java index c9a8ae691e..4822a52e12 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java @@ -40,12 +40,17 @@ public class ResourceLoggerTest { System.setProperty("log4j2.loggerContextFactory", TestLoggerContextFactory.class.getName()); } + @BeforeAll + public static void afterAll() { + System.clearProperty("log4j2.loggerContextFactory"); + } + @Test public void testFactory() throws Exception { Connection connection = new Connection("Test", "dummy"); connection.useConnection(); MapSupplier mapSupplier = new MapSupplier(connection); - ResourceLogger logger = ResourceLogger.newBuilder() + Logger logger = ResourceLogger.newBuilder() .withClass(this.getClass()) .withSupplier(mapSupplier) .build(); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ContextData.java b/log4j-api/src/main/java/org/apache/logging/log4j/ContextData.java new file mode 100644 index 0000000000..1b9445dcd8 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/ContextData.java @@ -0,0 +1,106 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.spi.ContextDataProvider; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.ServiceLoaderUtil; +import org.apache.logging.log4j.util.StringMap; + +/** + * General purpose utility class for accessing data accessible through ContextDataProviders. + */ +public final class ContextData { + + private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * ContextDataProviders loaded via OSGi. + */ + public static Collection<ContextDataProvider> contextDataProviders = new ConcurrentLinkedDeque<>(); + + private static final List<ContextDataProvider> SERVICE_PROVIDERS = getServiceProviders(); + + private ContextData() {} + + private static List<ContextDataProvider> getServiceProviders() { + final List<ContextDataProvider> providers = new ArrayList<>(); + ServiceLoaderUtil.safeStream( + ContextDataProvider.class, + ServiceLoader.load(ContextDataProvider.class, ContextData.class.getClassLoader()), + LOGGER) + .forEach(providers::add); + return Collections.unmodifiableList(providers); + } + + public static void addProvider(ContextDataProvider provider) { + contextDataProviders.add(provider); + } + + private static List<ContextDataProvider> getProviders() { + final List<ContextDataProvider> providers = + new ArrayList<>(contextDataProviders.size() + SERVICE_PROVIDERS.size()); + providers.addAll(contextDataProviders); + providers.addAll(SERVICE_PROVIDERS); + return providers; + } + + public static int size() { + final List<ContextDataProvider> providers = getProviders(); + final AtomicInteger count = new AtomicInteger(0); + providers.forEach((provider) -> count.addAndGet(provider.size())); + return count.get(); + } + + /** + * Populates the provided StringMap with data from the Context. + * @param stringMap the StringMap to contain the results. + */ + public static void addAll(StringMap stringMap) { + final List<ContextDataProvider> providers = getProviders(); + providers.forEach((provider) -> provider.addAll(stringMap)); + } + + /** + * Populates the provided Map with data from the Context. + * @param map the Map to contain the results. + * @return the Map. Useful for chaining operations. + */ + public static Map<String, String> addAll(Map<String, String> map) { + final List<ContextDataProvider> providers = getProviders(); + providers.forEach((provider) -> provider.addAll(map)); + return map; + } + + public static String getValue(String key) { + List<ContextDataProvider> providers = getProviders(); + for (ContextDataProvider provider : providers) { + String value = provider.get(key); + if (value != null) { + return value; + } + } + return null; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ResourceLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/ResourceLogger.java index 17320a74af..7a30dab94d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/ResourceLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/ResourceLogger.java @@ -28,8 +28,7 @@ import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; /** - * Logger for resources. Formats all events using the ParameterizedMapMessageFactory along with the provided - * Supplier. The Supplier provides resource attributes that should be included in all log events generated + * Logger for resources. The Supplier provides resource attributes that should be included in all log events generated * from the current resource. Note that since the Supplier is called for every LogEvent being generated * the values returned may change as necessary. Care should be taken to make the Supplier as efficient as * possible to avoid performance issues. @@ -137,7 +136,7 @@ public final class ResourceLogger extends ExtendedLoggerWrapper { * Construct the ResourceLogger. * @return the ResourceLogger. */ - public ResourceLogger build() { + public Logger build() { if (this.logger == null) { if (Strings.isEmpty(name)) { Class<?> clazz = StackLocatorUtil.getCallerClass(2); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ScopedContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/ScopedContext.java index b105e3be1d..67e2ba97fd 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/ScopedContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/ScopedContext.java @@ -25,7 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.function.Supplier; -import org.apache.logging.log4j.internal.ScopedContextAnchor; +import org.apache.logging.log4j.spi.ScopedContextDataProvider; import org.apache.logging.log4j.status.StatusLogger; /** @@ -59,7 +59,7 @@ public class ScopedContext { * @return the Map of Renderable objects. */ public static Map<String, Renderable> getContextMap() { - Optional<Instance> context = ScopedContextAnchor.getContext(); + Optional<Instance> context = ScopedContextDataProvider.getContext(); if (context.isPresent() && context.get().contextMap != null && !context.get().contextMap.isEmpty()) { @@ -69,13 +69,23 @@ public class ScopedContext { } /** - * Return the key from the current ScopedContext, if there is one and the key exists. + * @hidden + * Returns the number of entries in the context map. + * @return the number of items in the context map. + */ + public static int size() { + Optional<Instance> context = ScopedContextDataProvider.getContext(); + return context.map(instance -> instance.contextMap.size()).orElse(0); + } + + /** + * Return the value of the key from the current ScopedContext, if there is one and the key exists. * @param key The key. * @return The value of the key in the current ScopedContext. */ @SuppressWarnings("unchecked") public static <T> T get(String key) { - Optional<Instance> context = ScopedContextAnchor.getContext(); + Optional<Instance> context = ScopedContextDataProvider.getContext(); if (context.isPresent()) { Renderable renderable = context.get().contextMap.get(key); if (renderable != null) { @@ -85,6 +95,36 @@ public class ScopedContext { return null; } + /** + * Return String value of the key from the current ScopedContext, if there is one and the key exists. + * @param key The key. + * @return The value of the key in the current ScopedContext. + */ + public static String getString(String key) { + Optional<Instance> context = ScopedContextDataProvider.getContext(); + if (context.isPresent()) { + Renderable renderable = context.get().contextMap.get(key); + if (renderable != null) { + return renderable.render(); + } + } + return null; + } + + /** + * Adds all the String rendered objects in the context map to the provided Map. + * @param map The Map to add entries to. + */ + public static void addAll(Map<String, String> map) { + Optional<Instance> context = ScopedContextDataProvider.getContext(); + if (context.isPresent()) { + Map<String, Renderable> contextMap = context.get().contextMap; + if (contextMap != null && !contextMap.isEmpty()) { + contextMap.forEach((key, value) -> map.put(key, value.render())); + } + } + } + /** * Creates a ScopedContext Instance with a key/value pair. * @@ -316,7 +356,7 @@ public class ScopedContext { * @return an Optional containing the active ScopedContext, if there is one. */ private static Optional<Instance> current() { - return ScopedContextAnchor.getContext(); + return ScopedContextDataProvider.getContext(); } public static class Instance { @@ -460,11 +500,11 @@ public class ScopedContext { if (contextStack != null) { ThreadContext.setStack(contextStack); } - ScopedContextAnchor.addScopedContext(scopedContext); + ScopedContextDataProvider.addScopedContext(scopedContext); try { op.run(); } finally { - ScopedContextAnchor.removeScopedContext(); + ScopedContextDataProvider.removeScopedContext(); ThreadContext.clearAll(); } } @@ -511,11 +551,11 @@ public class ScopedContext { if (contextStack != null) { ThreadContext.setStack(contextStack); } - ScopedContextAnchor.addScopedContext(scopedContext); + ScopedContextDataProvider.addScopedContext(scopedContext); try { return op.call(); } finally { - ScopedContextAnchor.removeScopedContext(); + ScopedContextDataProvider.removeScopedContext(); ThreadContext.clearAll(); } } @@ -523,6 +563,10 @@ public class ScopedContext { /** * Interface for converting Objects stored in the ContextScope to Strings for logging. + * + * Users implementing this interface are encouraged to make the render method as lightweight as possible, + * Typically by creating the String representation of the object during its construction and just returning + * the String. */ public static interface Renderable { /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java index f5529f4258..d914f1c4c9 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java @@ -23,10 +23,9 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ScopedContext; -import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; @@ -296,8 +295,8 @@ public class SimpleLogger extends AbstractLogger { } sb.append(msg.getFormattedMessage()); if (showContextMap) { - final Map<String, String> mdc = new HashMap<>(ThreadContext.getImmutableContext()); - ScopedContext.getContextMap().forEach((key, value) -> mdc.put(key, value.render())); + final Map<String, String> mdc = new HashMap<>(ContextData.size()); + ContextData.addAll(mdc); if (!mdc.isEmpty()) { sb.append(SPACE); sb.append(mdc.toString()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextDataProvider.java similarity index 54% copy from log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java copy to log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextDataProvider.java index 8ac63b6858..d003e9c74f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextDataProvider.java @@ -14,10 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.spi; import java.util.Map; -import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap; import org.apache.logging.log4j.util.StringMap; /** @@ -25,6 +24,15 @@ import org.apache.logging.log4j.util.StringMap; */ public interface ContextDataProvider { + /** + * Returns the key for a value from the context data. + * @param key the key to locate. + * @return the value or null if it is not found. + */ + default String get(String key) { + return null; + } + /** * Returns a Map containing context data to be injected into the event or null if no context data is to be added. * <p> @@ -36,14 +44,33 @@ public interface ContextDataProvider { Map<String, String> supplyContextData(); /** - * Returns the context data as a StringMap. - * <p> - * Thread-safety note: The returned object can safely be passed off to another thread: future changes in the - * underlying context data will not be reflected in the returned object. - * </p> - * @return the context data in a StringMap. + * Returns the number of items in this context. + * @return the number of items in the context. + */ + default int size() { + Map<String, String> contextMap = supplyContextData(); + return contextMap != null ? contextMap.size() : 0; + } + + /** + * Add all the keys in the current context to the provided Map. + * @param map the StringMap to add the keys and values to. + */ + default void addAll(Map<String, String> map) { + Map<String, String> contextMap = supplyContextData(); + if (contextMap != null) { + map.putAll(contextMap); + } + } + + /** + * Add all the keys in the current context to the provided StringMap. + * @param map the StringMap to add the keys and values to. */ - default StringMap supplyStringMap() { - return new JdkMapAdapterStringMap(supplyContextData(), true); + default void addAll(StringMap map) { + Map<String, String> contextMap = supplyContextData(); + if (contextMap != null) { + contextMap.forEach(map::putValue); + } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ScopedContextAnchor.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ScopedContextDataProvider.java similarity index 66% rename from log4j-api/src/main/java/org/apache/logging/log4j/internal/ScopedContextAnchor.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/ScopedContextDataProvider.java index c09c4bc78f..f2a14c19d0 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ScopedContextAnchor.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ScopedContextDataProvider.java @@ -14,17 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.internal; +package org.apache.logging.log4j.spi; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; import java.util.ArrayDeque; +import java.util.Collections; import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.apache.logging.log4j.ScopedContext; /** - * Anchor for the ScopedContext. This class is private and not for public consumption. + * ContextDataProvider for {@code Map<String, String>} data. + * @since 2.24.0 */ -public class ScopedContextAnchor { +@ServiceProvider(value = ContextDataProvider.class, resolution = Resolution.OPTIONAL) +public class ScopedContextDataProvider implements ContextDataProvider { + private static final ThreadLocal<Deque<ScopedContext.Instance>> scopedContext = new ThreadLocal<>(); /** @@ -66,4 +74,31 @@ public class ScopedContextAnchor { } } } + + @Override + public String get(String key) { + return ScopedContext.getString(key); + } + + @Override + public Map<String, String> supplyContextData() { + Map<String, ScopedContext.Renderable> contextMap = ScopedContext.getContextMap(); + if (!contextMap.isEmpty()) { + Map<String, String> map = new HashMap<>(); + contextMap.forEach((key, value) -> map.put(key, value.render())); + return map; + } else { + return Collections.emptyMap(); + } + } + + @Override + public int size() { + return ScopedContext.size(); + } + + @Override + public void addAll(Map<String, String> map) { + ScopedContext.addAll(map); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ScopedContextDataProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextDataProvider.java similarity index 56% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ScopedContextDataProvider.java rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextDataProvider.java index a4f651b7ea..d6e28f00d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ScopedContextDataProvider.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextDataProvider.java @@ -14,37 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.impl; +package org.apache.logging.log4j.spi; import aQute.bnd.annotation.Resolution; import aQute.bnd.annotation.spi.ServiceProvider; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.ScopedContext; -import org.apache.logging.log4j.core.util.ContextDataProvider; -import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.ThreadContext; /** - * ContextDataProvider for {@code Map<String, String>} data. + * ContextDataProvider for ThreadContext data. */ @ServiceProvider(value = ContextDataProvider.class, resolution = Resolution.OPTIONAL) -public class ScopedContextDataProvider implements ContextDataProvider { +public class ThreadContextDataProvider implements ContextDataProvider { + + @Override + public String get(String key) { + return ThreadContext.get(key); + } @Override public Map<String, String> supplyContextData() { - Map<String, ScopedContext.Renderable> contextMap = ScopedContext.getContextMap(); - if (!contextMap.isEmpty()) { - Map<String, String> map = new HashMap<>(); - contextMap.forEach((key, value) -> map.put(key, value.render())); - return map; - } else { - return Collections.emptyMap(); - } + return ThreadContext.getImmutableContext(); + } + + @Override + public int size() { + return ThreadContext.getContext().size(); } @Override - public StringMap supplyStringMap() { - return new JdkMapAdapterStringMap(supplyContextData()); + public void addAll(Map<String, String> map) { + map.putAll(ThreadContext.getContext()); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java index a66f4459ac..738abeec7b 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/ResourceLoggerTest.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ResourceLogger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; @@ -54,7 +55,7 @@ public class ResourceLoggerTest { Connection connection = new Connection("Test", "dummy"); connection.useConnection(); MapSupplier mapSupplier = new MapSupplier(connection); - ResourceLogger logger = ResourceLogger.newBuilder() + Logger logger = ResourceLogger.newBuilder() .withClass(this.getClass()) .withSupplier(mapSupplier) .build(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java index 39258f5003..9465460823 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java @@ -91,7 +91,7 @@ public class ThreadContextDataInjectorTest { : readOnlythreadContextMap.getClass().getName(), is(equalTo(readOnlythreadContextMapClassName))); - final ContextDataInjector contextDataInjector = createInjector(); + final ContextDataInjector contextDataInjector = createInjector(true); final StringMap stringMap = contextDataInjector.injectContextData(null, new SortedArrayStringMap()); assertThat("thread context map", ThreadContext.getContext(), allOf(hasEntry("foo", "bar"), not(hasKey("baz")))); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java index 6d386fe139..56293f59c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java @@ -21,6 +21,7 @@ import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.impl.ThreadContextDataInjector; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.SortedArrayStringMap; import org.apache.logging.log4j.util.StringMap; /** @@ -105,6 +106,19 @@ public interface ContextDataInjector { * the implementation of this method. It is not safe to pass the returned object to another thread. * </p> * @return a {@code ReadOnlyStringMap} object reflecting the current state of the context, may not return {@code null} + * @deprecated - Methods using this have been converted to call getValue(). Will be removed in 3.0.0. */ - ReadOnlyStringMap rawContextData(); + @Deprecated + default ReadOnlyStringMap rawContextData() { + return new SortedArrayStringMap(); + } + + /** + * Retrieves a key from the context. This avoids having to construct a composite Map when multiple contexts are available. + * @param key the key to retrieve. + * @return the String value associated with the key. + */ + default String getValue(String key) { + return null; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java index 0378d010cc..5bb479f66c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java @@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.async; import com.lmax.disruptor.EventTranslatorVararg; import com.lmax.disruptor.dsl.Disruptor; import java.util.List; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -484,6 +485,11 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf final Thread currentThread = Thread.currentThread(); final String threadName = THREAD_NAME_CACHING_STRATEGY.getThreadName(); + if (CONTEXT_DATA_INJECTOR == null) { + ContextData.addAll((StringMap) event.getContextData()); + } else { + CONTEXT_DATA_INJECTOR.injectContextData(null, (StringMap) event.getContextData()); + } event.setValues( asyncLogger, asyncLogger.getName(), @@ -492,9 +498,7 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf level, message, thrown, - // config properties are taken care of in the EventHandler thread - // in the AsyncLogger#actualAsyncLog method - CONTEXT_DATA_INJECTOR.injectContextData(null, (StringMap) event.getContextData()), + null, contextStack, currentThread.getId(), threadName, diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java index d3ab5a10e8..ae8e1bce94 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java @@ -121,7 +121,9 @@ public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequen this.marker = aMarker; this.fqcn = theFqcn; this.location = aLocation; - this.contextData = mutableContextData; + if (mutableContextData != null) { + this.contextData = mutableContextData; + } this.contextStack = aContextStack; this.asyncLogger = anAsyncLogger; this.populated = true; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java index 9763ff7fce..c965ce974d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core.async; import com.lmax.disruptor.EventTranslator; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext.ContextStack; @@ -57,6 +58,13 @@ public class RingBufferLogEventTranslator implements EventTranslator<RingBufferL public void translateTo(final RingBufferLogEvent event, final long sequence) { try { final ReadOnlyStringMap contextData = event.getContextData(); + if (contextData != null) { + if (INJECTOR == null) { + ContextData.addAll((StringMap) contextData); + } else { + INJECTOR.injectContextData(null, (StringMap) contextData); + } + } event.setValues( asyncLogger, loggerName, @@ -67,7 +75,7 @@ public class RingBufferLogEventTranslator implements EventTranslator<RingBufferL thrown, // config properties are taken care of in the EventHandler thread // in the AsyncLogger#actualAsyncLog method - INJECTOR.injectContextData(null, contextData instanceof StringMap ? (StringMap) contextData : null), + null, contextStack, threadId, threadName, diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java index 25187b0f17..d2de85fe4d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java @@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.filter; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -35,14 +36,14 @@ import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.spi.ContextDataProvider; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StringMap; /** * Compares against a log level that is associated with a context value. By default the context is the - * {@link ThreadContext}, but users may {@linkplain ContextDataInjectorFactory configure} a custom - * {@link ContextDataInjector} which obtains context data from some other source. + * {@link ThreadContext} and/or the {@link org.apache.logging.log4j.ScopedContext}, but users may add a custom + * {@link ContextDataProvider} which obtains context data from some other source. */ @Plugin( name = "DynamicThresholdFilter", @@ -125,8 +126,7 @@ public final class DynamicThresholdFilter extends AbstractFilter { return true; } - private Result filter(final Level level, final ReadOnlyStringMap contextMap) { - final String value = contextMap.getValue(key); + private Result filter(final Level level, String value) { if (value != null) { Level ctxLevel = levelMap.get(value); if (ctxLevel == null) { @@ -139,7 +139,7 @@ public final class DynamicThresholdFilter extends AbstractFilter { @Override public Result filter(final LogEvent event) { - return filter(event.getLevel(), event.getContextData()); + return filter(event.getLevel(), (String) event.getContextData().getValue(key)); } @Override @@ -160,8 +160,11 @@ public final class DynamicThresholdFilter extends AbstractFilter { return filter(level, currentContextData()); } - private ReadOnlyStringMap currentContextData() { - return injector.rawContextData(); + private String currentContextData() { + if (injector == null) { + return ContextData.getValue(key); + } + return injector.rawContextData().getValue(key); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java index 21e6e1da1b..7b27f88450 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java @@ -21,9 +21,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; @@ -33,14 +33,10 @@ import org.apache.logging.log4j.core.config.plugins.PluginAliases; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.impl.ContextDataFactory; -import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.StringMap; /** * Filter based on a value in the Thread Context Map (MDC). @@ -53,7 +49,6 @@ import org.apache.logging.log4j.util.StringMap; @PluginAliases("ContextMapFilter") @PerformanceSensitive("allocation") public class ThreadContextMapFilter extends MapFilter { - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); private final String key; private final String value; @@ -62,12 +57,6 @@ public class ThreadContextMapFilter extends MapFilter { public ThreadContextMapFilter( final Map<String, List<String>> pairs, final boolean oper, final Result onMatch, final Result onMismatch) { super(pairs, oper, onMatch, onMismatch); - // ContextDataFactory looks up a property. The Spring PropertySource may log which will cause recursion. - // By initializing the ContextDataFactory here recursion will be prevented. - final StringMap map = ContextDataFactory.createContextData(); - LOGGER.debug( - "Successfully initialized ContextDataFactory by retrieving the context data with {} entries", - map.size()); if (pairs.size() == 1) { final Iterator<Map.Entry<String, List<String>>> iter = pairs.entrySet().iterator(); @@ -109,28 +98,20 @@ public class ThreadContextMapFilter extends MapFilter { private Result filter() { boolean match = false; if (useMap) { - ReadOnlyStringMap currentContextData = null; final IndexedReadOnlyStringMap map = getStringMap(); for (int i = 0; i < map.size(); i++) { - if (currentContextData == null) { - currentContextData = currentContextData(); - } - final String toMatch = currentContextData.getValue(map.getKeyAt(i)); + final String toMatch = ContextData.getValue(map.getKeyAt(i)); match = toMatch != null && ((List<String>) map.getValueAt(i)).contains(toMatch); if ((!isAnd() && match) || (isAnd() && !match)) { break; } } } else { - match = value.equals(currentContextData().getValue(key)); + match = value.equals(ContextData.getValue(key)); } return match ? onMatch : onMismatch; } - private ReadOnlyStringMap currentContextData() { - return injector.rawContextData(); - } - @Override public Result filter(final LogEvent event) { return super.filter(event.getContextData()) ? onMatch : onMismatch; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java index 8ad77df471..e84cfe7671 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java @@ -22,7 +22,7 @@ * {@link org.apache.logging.log4j.core.Filter#ELEMENT_TYPE filter}. */ @Export -@Version("2.21.0") +@Version("2.24.0") package org.apache.logging.log4j.core.filter; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java index 141604810c..aa73324e8b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java @@ -30,27 +30,25 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap; * Factory for ContextDataInjectors. Returns a new {@code ContextDataInjector} instance based on the value of system * property {@code log4j2.ContextDataInjector}. Users may use this system property to specify the fully qualified class * name of a class that implements the {@code ContextDataInjector} interface. - * If no value was specified this factory method returns one of the injectors defined in - * {@code ThreadContextDataInjector}. * * @see ContextDataInjector * @see ReadOnlyStringMap * @see ThreadContextDataInjector * @see LogEvent#getContextData() * @since 2.7 + * @deprecated Use ContextDataProvider instead. */ +@Deprecated public class ContextDataInjectorFactory { private static final String CONTEXT_DATA_INJECTOR_PROPERTY = "log4j2.ContextDataInjector"; /** * Returns a new {@code ContextDataInjector} instance based on the value of system property - * {@code log4j2.ContextDataInjector}. If no value was specified this factory method returns one of the - * {@code ContextDataInjector} classes defined in {@link ThreadContextDataInjector} which is most appropriate for - * the ThreadContext implementation. - * <p> + * {@code log4j2.ContextDataInjector}. If no value was specified then @{link ContextData} will be used. * <b>Note:</b> It is no longer recommended that users provide a custom implementation of the ContextDataInjector. - * Instead, provide a {@code ContextDataProvider}. + * Instead, provide a {@code ContextDataProvider}. Support for ContextDataInjectors will be removed entirely + * in 3.0.0. * </p> * <p> * Users may use this system property to specify the fully qualified class name of a class that implements the @@ -65,16 +63,23 @@ public class ContextDataInjectorFactory { * @return a ContextDataInjector that populates the {@code ReadOnlyStringMap} of all {@code LogEvent} objects * @see LogEvent#getContextData() * @see ContextDataInjector + * @deprecated Uses ContextData instead. */ + @Deprecated public static ContextDataInjector createInjector() { + return createInjector(false); + } + + @Deprecated + public static ContextDataInjector createInjector(final boolean useDefault) { try { return LoaderUtil.newCheckedInstanceOfProperty( CONTEXT_DATA_INJECTOR_PROPERTY, ContextDataInjector.class, - ContextDataInjectorFactory::createDefaultInjector); + useDefault ? ContextDataInjectorFactory::createDefaultInjector : () -> null); } catch (final ReflectiveOperationException e) { - StatusLogger.getLogger().warn("Could not create ContextDataInjector: {}", e.getMessage(), e); - return createDefaultInjector(); + StatusLogger.getLogger().info("Could not create ContextDataInjector: {}", e.getMessage(), e); + return null; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java index 09c4441394..73853865e6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java @@ -47,7 +47,7 @@ public class JdkMapAdapterStringMap implements StringMap { // It is a cache, no need to synchronise it between threads. private static Map<Class<?>, Void> UNMODIFIABLE_MAPS_CACHE = new WeakHashMap<>(); - private final Map<String, String> map; + protected final Map<String, String> map; private boolean immutable = false; private transient String[] sortedKeys; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java index 7185bc7bc8..bc02b085bb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java @@ -24,6 +24,7 @@ import java.rmi.MarshalledObject; import java.util.List; import java.util.Map; import java.util.Objects; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -680,6 +681,11 @@ public class Log4jLogEvent implements LogEvent { private static StringMap createContextData(final List<Property> properties) { final StringMap reusable = ContextDataFactory.createContextData(); + if (CONTEXT_DATA_INJECTOR == null) { + copyProperties(properties, reusable); + ContextData.addAll(reusable); + return reusable; + } return CONTEXT_DATA_INJECTOR.injectContextData(properties, reusable); } @@ -979,6 +985,21 @@ public class Log4jLogEvent implements LogEvent { throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString()); } + /** + * Copies key-value pairs from the specified property list into the specified {@code StringMap}. + * + * @param properties list of configuration properties, may be {@code null} + * @param result the {@code StringMap} object to add the key-values to. Must be non-{@code null}. + */ + private static void copyProperties(final List<Property> properties, final StringMap result) { + if (properties != null) { + for (int i = 0; i < properties.size(); i++) { + final Property prop = properties.get(i); + result.putValue(prop.getName(), prop.getValue()); + } + } + } + private void readObject(final ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } 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 e954d9b7f7..5a916d4003 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 @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core.impl; import java.util.List; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -100,7 +101,13 @@ public class ReusableLogEventFactory implements LogEventFactory, LocationAwareLo result.initTime(CLOCK, Log4jLogEvent.getNanoClock()); result.setThrown(t); result.setSource(location); - result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData())); + if (injector != null) { + result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData())); + } else { + StringMap reusable = (StringMap) result.getContextData(); + copyProperties(properties, reusable); + ContextData.addAll(reusable); + } result.setContextStack( ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack()); // mutable copy @@ -111,6 +118,21 @@ public class ReusableLogEventFactory implements LogEventFactory, LocationAwareLo return result; } + /** + * Copies key-value pairs from the specified property list into the specified {@code StringMap}. + * + * @param properties list of configuration properties, may be {@code null} + * @param result the {@code StringMap} object to add the key-values to. Must be non-{@code null}. + */ + private static void copyProperties(final List<Property> properties, final StringMap result) { + if (properties != null) { + for (int i = 0; i < properties.size(); i++) { + final Property prop = properties.get(i); + result.putValue(prop.getName(), prop.getValue()); + } + } + } + private static MutableLogEvent getOrCreateMutableLogEvent() { final MutableLogEvent result = mutableLogEventThreadLocal.get(); return result == null || result.reserved ? createInstance(result) : result; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java index 332b877a51..839c3c2df5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java @@ -16,17 +16,12 @@ */ package org.apache.logging.log4j.core.impl; -import aQute.bnd.annotation.Cardinality; -import aQute.bnd.annotation.Resolution; -import aQute.bnd.annotation.spi.ServiceConsumer; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; -import java.util.concurrent.ConcurrentLinkedDeque; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; @@ -35,7 +30,6 @@ import org.apache.logging.log4j.core.util.ContextDataProvider; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.ServiceLoaderUtil; import org.apache.logging.log4j.util.StringMap; /** @@ -43,7 +37,8 @@ import org.apache.logging.log4j.util.StringMap; * {@code ThreadContext} map implementations into a {@code StringMap}. In the case of duplicate keys, * thread context values overwrite configuration {@code Property} values. * <p> - * These are the default {@code ContextDataInjector} objects returned by the {@link ContextDataInjectorFactory}. + * This class is no longer directly used by Log4j. It is only present in case it is being overridden by a user. + * Will be removed in 3.0.0. * </p> * * @see org.apache.logging.log4j.ThreadContext @@ -52,11 +47,9 @@ import org.apache.logging.log4j.util.StringMap; * @see ContextDataInjector * @see ContextDataInjectorFactory * @since 2.7 + * @Deprecated Use @{link ContextData} instead. */ -@ServiceConsumer( - value = ContextDataProvider.class, - resolution = Resolution.OPTIONAL, - cardinality = Cardinality.MULTIPLE) +@Deprecated public class ThreadContextDataInjector { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -64,9 +57,7 @@ public class ThreadContextDataInjector { /** * ContextDataProviders loaded via OSGi. */ - public static Collection<ContextDataProvider> contextDataProviders = new ConcurrentLinkedDeque<>(); - - private static final List<ContextDataProvider> SERVICE_PROVIDERS = getServiceProviders(); + public static Collection<ContextDataProvider> contextDataProviders = new ProviderQueue(); /** * Previously this method allowed ContextDataProviders to be loaded eagerly, now they @@ -77,16 +68,6 @@ public class ThreadContextDataInjector { @Deprecated public static void initServiceProviders() {} - private static List<ContextDataProvider> getServiceProviders() { - final List<ContextDataProvider> providers = new ArrayList<>(); - ServiceLoaderUtil.safeStream( - ContextDataProvider.class, - ServiceLoader.load(ContextDataProvider.class, ThreadContextDataInjector.class.getClassLoader()), - LOGGER) - .forEach(providers::add); - return Collections.unmodifiableList(providers); - } - /** * Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is * also the ThreadContext implementation used for web applications). @@ -95,11 +76,7 @@ public class ThreadContextDataInjector { */ public static class ForDefaultThreadContextMap implements ContextDataInjector { - private final List<ContextDataProvider> providers; - - public ForDefaultThreadContextMap() { - providers = getProviders(); - } + public ForDefaultThreadContextMap() {} /** * Puts key-value pairs from both the specified list of properties as well as the thread context into the @@ -111,44 +88,12 @@ public class ThreadContextDataInjector { */ @Override public StringMap injectContextData(final List<Property> props, final StringMap ignore) { - - final Map<String, String> copy; - - if (providers.size() == 1) { - copy = providers.get(0).supplyContextData(); - } else { - copy = new HashMap<>(); - for (ContextDataProvider provider : providers) { - copy.putAll(provider.supplyContextData()); - } - } - - // The DefaultThreadContextMap stores context data in a Map<String, String>. - // This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy. - // If there are no configuration properties or providers returning a thin wrapper around the copy - // is faster than copying the elements into the LogEvent's reusable StringMap. - if ((props == null || props.isEmpty())) { - // this will replace the LogEvent's context data with the returned instance. - // NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx) - return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy); - } - // If the list of Properties is non-empty we need to combine the properties and the ThreadContext - // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined - // and others not, so the LogEvent's context data may have been replaced with an immutable copy from - // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it. - final StringMap result = new JdkMapAdapterStringMap(new HashMap<>(copy), false); - for (int i = 0; i < props.size(); i++) { - final Property prop = props.get(i); - if (!copy.containsKey(prop.getName())) { - result.putValue(prop.getName(), prop.getValue()); - } - } - result.freeze(); - return result; - } - - private static JdkMapAdapterStringMap frozenStringMap(final Map<String, String> copy) { - return new JdkMapAdapterStringMap(copy, true); + Map<String, String> map = new HashMap<>(); + JdkMapAdapterStringMap stringMap = new JdkMapAdapterStringMap(map, false); + copyProperties(props, stringMap); + ContextData.addAll(map); + stringMap.freeze(); + return stringMap; } @Override @@ -172,11 +117,8 @@ public class ThreadContextDataInjector { * This injector always puts key-value pairs into the specified reusable StringMap. */ public static class ForGarbageFreeThreadContextMap implements ContextDataInjector { - private final List<ContextDataProvider> providers; - public ForGarbageFreeThreadContextMap() { - this.providers = getProviders(); - } + public ForGarbageFreeThreadContextMap() {} /** * Puts key-value pairs from both the specified list of properties as well as the thread context into the @@ -192,12 +134,13 @@ public class ThreadContextDataInjector { // StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later // and such modifications should not be reflected in the log event. copyProperties(props, reusable); - for (int i = 0; i < providers.size(); ++i) { - reusable.putAll(providers.get(i).supplyStringMap()); - } + ContextData.addAll(reusable); return reusable; } + /* + No longer used. + */ @Override public ReadOnlyStringMap rawContextData() { return ThreadContext.getThreadContextMap().getReadOnlyContextData(); @@ -205,59 +148,9 @@ public class ThreadContextDataInjector { } /** - * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a copy-on-write - * StringMap-based data structure. - * <p> - * If there are no configuration properties, this injector will return the thread context's internal data - * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the - * specified reusable StringMap. + * Th */ - public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector { - private final List<ContextDataProvider> providers; - - public ForCopyOnWriteThreadContextMap() { - this.providers = getProviders(); - } - /** - * If there are no configuration properties, this injector will return the thread context's internal data - * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the - * specified reusable StringMap. - * - * @param props list of configuration properties, may be {@code null} - * @param ignore a {@code StringMap} instance from the log event - * @return a {@code StringMap} combining configuration properties with thread context data - */ - @Override - public StringMap injectContextData(final List<Property> props, final StringMap ignore) { - // If there are no configuration properties we want to just return the ThreadContext's StringMap: - // it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy. - if (providers.size() == 1 && (props == null || props.isEmpty())) { - // this will replace the LogEvent's context data with the returned instance - return providers.get(0).supplyStringMap(); - } - int count = props == null ? 0 : props.size(); - final StringMap[] maps = new StringMap[providers.size()]; - for (int i = 0; i < providers.size(); ++i) { - maps[i] = providers.get(i).supplyStringMap(); - count += maps[i].size(); - } - // However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext - // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined - // and others not, so the LogEvent's context data may have been replaced with an immutable copy from - // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it. - final StringMap result = ContextDataFactory.createContextData(count); - copyProperties(props, result); - for (StringMap map : maps) { - result.putAll(map); - } - return result; - } - - @Override - public ReadOnlyStringMap rawContextData() { - return ThreadContext.getThreadContextMap().getReadOnlyContextData(); - } - } + public static class ForCopyOnWriteThreadContextMap extends ForDefaultThreadContextMap {} /** * Copies key-value pairs from the specified property list into the specified {@code StringMap}. @@ -274,11 +167,134 @@ public class ThreadContextDataInjector { } } - private static List<ContextDataProvider> getProviders() { - final List<ContextDataProvider> providers = - new ArrayList<>(contextDataProviders.size() + SERVICE_PROVIDERS.size()); - providers.addAll(contextDataProviders); - providers.addAll(SERVICE_PROVIDERS); - return providers; + private static class ProviderQueue implements Collection<ContextDataProvider> { + @Override + public int size() { + return ContextData.contextDataProviders.size(); + } + + @Override + public boolean isEmpty() { + return ContextData.contextDataProviders.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return ContextData.contextDataProviders.contains(o); + } + + @Override + public Iterator<ContextDataProvider> iterator() { + return new ProviderIterator(ContextData.contextDataProviders.iterator()); + } + + @Override + public Object[] toArray() { + return ContextData.contextDataProviders.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return ContextData.contextDataProviders.toArray(a); + } + + @Override + public boolean add(ContextDataProvider contextDataProvider) { + return ContextData.contextDataProviders.add(contextDataProvider); + } + + @Override + public boolean remove(Object o) { + return ContextData.contextDataProviders.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return ContextData.contextDataProviders.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends ContextDataProvider> c) { + return false; + } + + @Override + public boolean removeAll(Collection<?> c) { + return ContextData.contextDataProviders.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + return ContextData.contextDataProviders.retainAll(c); + } + + @Override + public void clear() { + ContextData.contextDataProviders.clear(); + } + } + + private static class ProviderIterator implements Iterator<ContextDataProvider> { + + private final Iterator<org.apache.logging.log4j.spi.ContextDataProvider> iter; + + public ProviderIterator(Iterator<org.apache.logging.log4j.spi.ContextDataProvider> iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public ContextDataProvider next() { + org.apache.logging.log4j.spi.ContextDataProvider next = iter.next(); + if (next instanceof ContextDataProvider) { + return (ContextDataProvider) next; + } else if (next != null) { + return new ProviderWrapper(next); + } + return null; + } + } + + private static class ProviderWrapper implements ContextDataProvider { + + private final org.apache.logging.log4j.spi.ContextDataProvider provider; + + public ProviderWrapper(org.apache.logging.log4j.spi.ContextDataProvider provider) { + this.provider = provider; + } + + @Override + public String get(String key) { + return provider.get(key); + } + + @Override + public int size() { + return provider.size(); + } + + @Override + public void addAll(Map<String, String> map) { + provider.addAll(map); + } + + @Override + public void addAll(StringMap map) { + provider.addAll(map); + } + + @Override + public Map<String, String> supplyContextData() { + return provider.supplyContextData(); + } + + @Override + public StringMap supplyStringMap() { + return new JdkMapAdapterStringMap(supplyContextData(), true); + } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java index 86778d2bd7..b24f1203a8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java @@ -16,17 +16,18 @@ */ package org.apache.logging.log4j.core.lookup; +import org.apache.logging.log4j.ContextData; +import org.apache.logging.log4j.ScopedContext; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; -import org.apache.logging.log4j.util.ReadOnlyStringMap; /** - * Looks up keys from the context. By default this is the {@link ThreadContext}, but users may - * {@linkplain ContextDataInjectorFactory configure} a custom {@link ContextDataInjector} which obtains context data - * from some other source. + * Looks up keys from the context. By default this is the {@link ThreadContext} or {@link ScopedContext}. Users may + * add their own {@link org.apache.logging.log4j.core.util.ContextDataProvider} which can be retrieved via this + * Lookup. */ @Plugin(name = "ctx", category = StrLookup.CATEGORY) public class ContextMapLookup implements StrLookup { @@ -40,11 +41,10 @@ public class ContextMapLookup implements StrLookup { */ @Override public String lookup(final String key) { - return currentContextData().getValue(key); - } - - private ReadOnlyStringMap currentContextData() { - return injector.rawContextData(); + if (injector == null) { + return ContextData.getValue(key); + } + return injector.getValue(key); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java index 12526349ce..d07f21e49e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java @@ -21,7 +21,7 @@ * {@link org.apache.logging.log4j.core.lookup.StrLookup#CATEGORY Lookup}. */ @Export -@Version("2.20.1") +@Version("2.24.0") package org.apache.logging.log4j.core.lookup; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java index 4fae66de22..17bde215ff 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java @@ -18,14 +18,14 @@ package org.apache.logging.log4j.core.osgi; import java.util.Collection; import java.util.concurrent.atomic.AtomicReference; +import org.apache.logging.log4j.ContextData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; import org.apache.logging.log4j.core.impl.Log4jProvider; -import org.apache.logging.log4j.core.impl.ThreadContextDataInjector; -import org.apache.logging.log4j.core.impl.ThreadContextDataProvider; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.core.util.ContextDataProvider; +import org.apache.logging.log4j.spi.ContextDataProvider; +import org.apache.logging.log4j.spi.ThreadContextDataProvider; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ProviderActivator; @@ -99,7 +99,7 @@ public final class Activator extends ProviderActivator implements SynchronousBun bundleContext.getServiceReferences(ContextDataProvider.class, null); for (final ServiceReference<ContextDataProvider> serviceReference : serviceReferences) { final ContextDataProvider provider = bundleContext.getService(serviceReference); - ThreadContextDataInjector.contextDataProviders.add(provider); + ContextData.addProvider(provider); } } catch (final InvalidSyntaxException ex) { LOGGER.error("Error accessing context data provider", ex); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java index a11875eea4..266256b463 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java @@ -18,7 +18,7 @@ * Implementation of Log4j 2. */ @Export -@Version("2.20.2") +@Version("2.24.0") package org.apache.logging.log4j.core; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java index 8ac63b6858..086ac93981 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java @@ -22,17 +22,11 @@ import org.apache.logging.log4j.util.StringMap; /** * Source of context data to be added to each log event. + * @deprecated Use ContextDataProvider from Log4j API from 2.24.0. */ -public interface ContextDataProvider { +@Deprecated +public interface ContextDataProvider extends org.apache.logging.log4j.spi.ContextDataProvider { - /** - * Returns a Map containing context data to be injected into the event or null if no context data is to be added. - * <p> - * Thread-safety note: The returned object can safely be passed off to another thread: future changes in the - * underlying context data will not be reflected in the returned object. - * </p> - * @return A Map containing the context data or null. - */ Map<String, String> supplyContextData(); /** @@ -42,7 +36,9 @@ public interface ContextDataProvider { * underlying context data will not be reflected in the returned object. * </p> * @return the context data in a StringMap. + * @deprecated No longer used since 2.24.0. Will be removed in 3.0.0. */ + @Deprecated default StringMap supplyStringMap() { return new JdkMapAdapterStringMap(supplyContextData(), true); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java index 753b7c456b..6c60254605 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java @@ -18,7 +18,7 @@ * Log4j 2 helper classes. */ @Export -@Version("2.20.2") +@Version("2.24.0") package org.apache.logging.log4j.core.util; import org.osgi.annotation.bundle.Export;
