You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2024/04/03 15:54:38 UTC
(logging-log4j2) 04/05: Move ContextDataProviders to the API
This is an automated email from the ASF dual-hosted git repository.
rgoers pushed a commit to branch ScopedContext
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 4490b4337e68d40c232bfe275f4eb3bbb01880b0
Author: Ralph Goers <rg...@apache.org>
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;