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 2020/03/03 06:50:09 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-2779 - Add ContextDataProviders as an alternative to having to implement a ContextDataInjector.

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new c6c2c0b  LOG4J2-2779 - Add ContextDataProviders as an alternative to having to implement a ContextDataInjector.
c6c2c0b is described below

commit c6c2c0b86c894c71dbf991cea299abbb4f4e011a
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Mon Mar 2 23:49:16 2020 -0700

    LOG4J2-2779 - Add ContextDataProviders as an alternative to having to implement a ContextDataInjector.
---
 .../logging/log4j/core/ContextDataInjector.java    |  3 +
 .../core/impl/ContextDataInjectorFactory.java      |  4 +
 .../log4j/core/impl/JdkMapAdapterStringMap.java    |  2 +-
 .../log4j/core/impl/ThreadContextDataInjector.java | 92 +++++++++++++++++++---
 .../log4j/core/impl/ThreadContextDataProvider.java | 39 +++++++++
 .../apache/logging/log4j/core/osgi/Activator.java  | 26 ++++++
 .../log4j/core/util/ContextDataProvider.java       | 42 ++++++++++
 ...che.logging.log4j.core.util.ContextDataProvider |  1 +
 .../log4j/core/util/ContextDataProviderTest.java   | 74 +++++++++++++++++
 .../src/test/resources/log4j-contextData.xml       | 29 +++++++
 src/changes/changes.xml                            |  7 +-
 src/site/site.xml                                  |  2 +-
 src/site/xdoc/manual/extending.xml                 | 40 ++++++----
 13 files changed, 329 insertions(+), 32 deletions(-)

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 5351245..83bf0fc 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
@@ -27,6 +27,9 @@ import org.apache.logging.log4j.util.StringMap;
 /**
  * Responsible for initializing the context data of LogEvents. Context data is data that is set by the application to be
  * included in all subsequent log events.
+ * <p><b>NOTE: It is no longer recommended that custom implementations of this interface be provided as it is
+ * difficult to do. Instead, provide a custom ContextDataProvider.</b></p>
+ * <p>
  * <p>
  * The source of the context data is implementation-specific. The default source for context data is the ThreadContext.
  * </p><p>
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 050d1e0..74925f6 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
@@ -48,6 +48,10 @@ public class ContextDataInjectorFactory {
      * {@code ContextDataInjector} classes defined in {@link ThreadContextDataInjector} which is most appropriate for
      * the ThreadContext implementation.
      * <p>
+     * <b>Note:</b> It is no longer recommended that users provide a custom implementation of the ContextDataInjector.
+     * Instead, provide a {@code ContextDataProvider}.
+     * </p>
+     * <p>
      * Users may use this system property to specify the fully qualified class name of a class that implements the
      * {@code ContextDataInjector} interface.
      * </p><p>
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 4091276..e86e4d6 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
@@ -30,7 +30,7 @@ import org.apache.logging.log4j.util.TriConsumer;
 /**
  * Provides a read-only {@code StringMap} view of a {@code Map<String, String>}.
  */
-class JdkMapAdapterStringMap implements StringMap {
+public class JdkMapAdapterStringMap implements StringMap {
     private static final long serialVersionUID = -7348247784983193612L;
     private static final String FROZEN = "Frozen collection cannot be modified";
     private static final Comparator<? super String> NULL_FIRST_COMPARATOR = new Comparator<String>() {
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 25f1d03..0522eca 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,14 +16,22 @@
  */
 package org.apache.logging.log4j.core.impl;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentLinkedDeque;
 
+import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.config.Property;
+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.LoaderUtil;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringMap;
 
@@ -44,6 +52,14 @@ import org.apache.logging.log4j.util.StringMap;
  */
 public class ThreadContextDataInjector {
 
+    private static Logger LOGGER = StatusLogger.getLogger();
+
+    /**
+     * ContextDataProviders loaded via OSGi.
+     */
+    public static Collection<ContextDataProvider> contextDataProviders =
+            new ConcurrentLinkedDeque<>();
+
     /**
      * Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is
      * also the ThreadContext implementation used for web applications).
@@ -52,24 +68,39 @@ public class ThreadContextDataInjector {
      */
     public static class ForDefaultThreadContextMap implements ContextDataInjector {
 
+        private final List<ContextDataProvider> providers;
+
+        public ForDefaultThreadContextMap() {
+            providers = getProviders();
+        }
+
         /**
          * Puts key-value pairs from both the specified list of properties as well as the thread context 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
+         * @param contextData 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) {
+        public StringMap injectContextData(final List<Property> props, final StringMap contextData) {
 
-            final Map<String, String> copy = ThreadContext.getImmutableContext();
+            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 returning a thin wrapper around the 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()) {
+            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);
@@ -114,6 +145,12 @@ 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();
+        }
+
         /**
          * Puts key-value pairs from both the specified list of properties as well as the thread context into the
          * specified reusable StringMap.
@@ -128,9 +165,9 @@ 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);
-
-            final ReadOnlyStringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
-            reusable.putAll(immutableCopy);
+            for (int i = 0; i < providers.size(); ++i) {
+                reusable.putAll(providers.get(i).supplyStringMap());
+            }
             return reusable;
         }
 
@@ -149,6 +186,11 @@ public class ThreadContextDataInjector {
      * specified reusable StringMap.
      */
     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
@@ -162,17 +204,25 @@ public class ThreadContextDataInjector {
         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.
-            final StringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
-            if (props == null || props.isEmpty()) {
-                return immutableCopy; // this will replace the LogEvent's context data with the returned instance
+            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.size();
+            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(props.size() + immutableCopy.size());
+            final StringMap result = ContextDataFactory.createContextData(count);
             copyProperties(props, result);
-            result.putAll(immutableCopy);
+            for (StringMap map : maps) {
+                result.putAll(map);
+            }
             return result;
         }
 
@@ -196,4 +246,20 @@ public class ThreadContextDataInjector {
             }
         }
     }
+
+    private static List<ContextDataProvider> getProviders() {
+        final List<ContextDataProvider> providers = new ArrayList<>(contextDataProviders);
+        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+            try {
+                for (final ContextDataProvider provider : ServiceLoader.load(ContextDataProvider.class, classLoader)) {
+                    if (providers.stream().noneMatch((p) -> p.getClass().isAssignableFrom(provider.getClass()))) {
+                        providers.add(provider);
+                    }
+                }
+            } catch (final Throwable ex) {
+                LOGGER.debug("Unable to access Context Data Providers {}", ex.getMessage());
+            }
+        }
+        return providers;
+    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java
new file mode 100644
index 0000000..230123e
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.util.ContextDataProvider;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.util.Map;
+
+/**
+ * ContextDataProvider for ThreadContext data.
+ */
+public class ThreadContextDataProvider implements ContextDataProvider {
+
+    @Override
+    public Map<String, String> supplyContextData() {
+        return ThreadContext.getImmutableContext();
+    }
+
+    @Override
+    public StringMap supplyStringMap() {
+        return ThreadContext.getThreadContextMap().getReadOnlyContextData();
+    }
+}
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 816ce37..24a6f60 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
@@ -17,14 +17,19 @@
 
 package org.apache.logging.log4j.core.osgi;
 
+import java.util.Collection;
 import java.util.Hashtable;
+import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.atomic.AtomicReference;
 
 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.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.PropertiesUtil;
@@ -32,6 +37,8 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.SynchronousBundleListener;
 import org.osgi.framework.wiring.BundleWiring;
@@ -46,13 +53,18 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
     private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
 
     ServiceRegistration provideRegistration = null;
+    ServiceRegistration contextDataRegistration = null;
 
     @Override
     public void start(final BundleContext context) throws Exception {
         final Provider provider = new Log4jProvider();
         final Hashtable<String, String> props = new Hashtable<>();
         props.put("APIVersion", "2.60");
+        final ContextDataProvider threadContextProvider = new ThreadContextDataProvider();
         provideRegistration = context.registerService(Provider.class.getName(), provider, props);
+        contextDataRegistration = context.registerService(ContextDataProvider.class.getName(), threadContextProvider,
+                null);
+        loadContextProviders(context);
         // allow the user to override the default ContextSelector (e.g., by using BasicContextSelector for a global cfg)
         if (PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR) == null) {
             System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BundleContextSelector.class.getName());
@@ -82,6 +94,19 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
         }
     }
 
+    private static void loadContextProviders(final BundleContext bundleContext) {
+        try {
+            final Collection<ServiceReference<ContextDataProvider>> serviceReferences =
+                    bundleContext.getServiceReferences(ContextDataProvider.class, null);
+            for (final ServiceReference<ContextDataProvider> serviceReference : serviceReferences) {
+                final ContextDataProvider provider = bundleContext.getService(serviceReference);
+                ThreadContextDataInjector.contextDataProviders.add(provider);
+            }
+        } catch (final InvalidSyntaxException ex) {
+            LOGGER.error("Error accessing context data provider", ex);
+        }
+    }
+
     private static void stopBundlePlugins(final Bundle bundle) {
         LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName());
         // TODO: plugin lifecycle code
@@ -91,6 +116,7 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
     @Override
     public void stop(final BundleContext context) throws Exception {
         provideRegistration.unregister();
+        contextDataRegistration.unregister();
         this.contextRef.compareAndSet(context, null);
         LogManager.shutdown();
     }
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
new file mode 100644
index 0000000..d302cf8
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.util.Map;
+
+/**
+ * Source of context data to be added to each log event.
+ */
+public interface ContextDataProvider {
+
+    /**
+     * Returns a Map containing context data to be injected into the event or null if no context data is to be added.
+     * @return A Map containing the context data or null.
+     */
+    Map<String, String> supplyContextData();
+
+    /**
+     * Returns the context data as a StringMap.
+     * @return the context data in a StringMap.
+     */
+    default StringMap supplyStringMap() {
+        return new JdkMapAdapterStringMap(supplyContextData());
+    }
+}
diff --git a/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider
new file mode 100644
index 0000000..7917658
--- /dev/null
+++ b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider
@@ -0,0 +1 @@
+org.apache.logging.log4j.core.impl.ThreadContextDataProvider
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java
new file mode 100644
index 0000000..f52c87d
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class ContextDataProviderTest {
+
+    private static Logger logger;
+    private static ListAppender appender;
+
+    @BeforeClass
+    public static void beforeClass() {
+        ThreadContextDataInjector.contextDataProviders.add(new TestContextDataProvider());
+        System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j-contextData.xml");
+        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
+        logger = loggerContext.getLogger(ContextDataProviderTest.class.getName());
+        appender = loggerContext.getConfiguration().getAppender("List");
+        assertNotNull("No List appender", appender);
+    }
+
+    @Test
+    public void testContextProvider() throws Exception {
+        ThreadContext.put("loginId", "jdoe");
+        logger.debug("This is a test");
+        List<String> messages = appender.getMessages();
+        assertEquals("Incorrect number of messages", 1, messages.size());
+        assertTrue("Context data missing", messages.get(0).contains("testKey=testValue"));
+    }
+
+    private static class TestContextDataProvider implements ContextDataProvider {
+
+        @Override
+        public Map<String, String> supplyContextData() {
+            Map<String, String> contextData = new HashMap<>();
+            contextData.put("testKey", "testValue");
+            return contextData;
+        }
+
+    }
+}
diff --git a/log4j-core/src/test/resources/log4j-contextData.xml b/log4j-core/src/test/resources/log4j-contextData.xml
new file mode 100644
index 0000000..80e83ee
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-contextData.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<Configuration status="WARN" name="Context Data Test">
+  <Appenders>
+    <List name="List">
+      <PatternLayout pattern="[%-5level] ThreadContext%X %c{1.} %msg%n" />
+    </List>
+  </Appenders>
+  <Loggers>
+    <Root level="debug">
+      <AppenderRef ref="List" />
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 871b463..4baa0f6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -29,7 +29,12 @@
          - "update" - Change
          - "remove" - Removed
     -->
-    <release version="2.13.1" date="2019-02-25" description="GA Release 2.13.1">
+    <release version="2.13.2" date="2020-MM-DD" description="GA Release 2.13.2">
+      <action issue="LOG4J2-2779" dev="rgoers" type="update">
+        Add ContextDataProviders as an alternative to having to implement a ContextDataInjector.
+      </action>
+    </release>
+    <release version="2.13.1" date="2020-02-25" description="GA Release 2.13.1">
       <action issue="LOG4J2-2717" dev="rgoers" type="fix">
         Slow initialization on Windows due to accessing network interfaces.
       </action>
diff --git a/src/site/site.xml b/src/site/site.xml
index d95e6de..cc11c8f 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -252,7 +252,7 @@
         <item name="Layouts" href="/manual/extending.html#Layouts"/>
         <item name="PatternConverters" href="/manual/extending.html#PatternConverters"/>
         <item name="Plugin Builders" href="/manual/extending.html#Plugin_Builders"/>
-        <item name="Custom ContextDataInjector" href="/manual/extending.html#Custom_ContextDataInjector"/>
+        <item name="Custom ContextDataProvider" href="/manual/extending.html#ContextDataProvider"/>
         <item name="Custom Plugins" href="/manual/extending.html#Custom_Plugins"/>
       </item>
 
diff --git a/src/site/xdoc/manual/extending.xml b/src/site/xdoc/manual/extending.xml
index 0f1fe27..ba04d68 100644
--- a/src/site/xdoc/manual/extending.xml
+++ b/src/site/xdoc/manual/extending.xml
@@ -70,6 +70,7 @@
               </li>
             </ol>
           </subsection>
+          <a name="ContextSelector"/>
           <subsection name="ContextSelector">
             <p>
               ContextSelectors are called by the
@@ -99,6 +100,7 @@
               call. This is enabled by default in OSGi environments.</dd>
             </dl>
           </subsection>
+          <a name="ConfigurationFactory"/>
           <subsection name="ConfigurationFactory">
             <p>
               Modifying the way in which logging can be configured is usually one of the areas with the most
@@ -151,6 +153,7 @@ public class XMLConfigurationFactory extends ConfigurationFactory {
     }
 }</pre>
           </subsection>
+          <a name="LoggerConfig"/>
           <subsection name="LoggerConfig">
             <p>
               LoggerConfig objects are where Loggers created by applications tie into the configuration. The Log4j
@@ -177,6 +180,7 @@ public static class RootLogger extends LoggerConfig {
     }
 }]]></pre>
           </subsection>
+          <a name="LogEventFactory"/>
           <subsection name="LogEventFactory">
             <p>A LogEventFactory is used to generate LogEvents. Applications may replace the standard LogEventFactory
               by setting the value of the system property Log4jLogEventFactory to the name of the custom
@@ -184,6 +188,7 @@ public static class RootLogger extends LoggerConfig {
             <p>Note: When log4j is configured to have <a href="async.html#AllAsync">all loggers asynchronous</a>,
               log events are pre-allocated in a ring buffer and the LogEventFactory is not used.</p>
           </subsection>
+        <a name="MessageFactory"/>
         <subsection name="MessageFactory">
           <p>A MessageFactory is used to generate Message objects. Applications may replace the standard
             ParameterizedMessageFactory (or ReusableMessageFactory in garbage-free mode)
@@ -194,6 +199,7 @@ public static class RootLogger extends LoggerConfig {
             log4j2.flowMessageFactory to the name of the custom FlowMessageFactory class.
           </p>
         </subsection>
+        <a name="Lookups"/>
           <subsection name="Lookups">
             <p>
               Lookups are the means in which parameter substitution is performed. During Configuration initialization
@@ -232,6 +238,7 @@ public class SystemPropertiesLookup implements StrLookup {
     }
 }</pre>
           </subsection>
+          <a name="Filters"/>
           <subsection name="Filters">
             <p>
               As might be expected, Filters are the used to reject or accept log events as they pass through the
@@ -298,6 +305,7 @@ public final class ThresholdFilter extends AbstractFilter {
     }
 }</pre>
           </subsection>
+          <a name="Appenders"/>
           <subsection name="Appenders">
             <p>
               Appenders are passed an event, (usually) invoke a Layout to format the event, and then "publish"
@@ -352,6 +360,7 @@ public final class StubAppender extends AbstractOutputStreamAppender<StubManager
     }
 }]]></pre>
           </subsection>
+          <a name="Layouts"/>
           <subsection name="Layouts">
             <p>
               Layouts perform the formatting of events into the printable text that is written by Appenders to
@@ -384,6 +393,7 @@ public class SampleLayout extends AbstractStringLayout {
     }
 }</pre>
           </subsection>
+        <a name="PatternConverters"/>
           <subsection name="PatternConverters">
             <p>
               PatternConverters are used by the PatternLayout to format the log event into a printable String. Each
@@ -419,6 +429,7 @@ public final class QueryConverter extends LogEventPatternConverter {
     }
 }</pre>
           </subsection>
+        <a name="Plugin_Builders"/>
           <subsection name="Plugin Builders">
             <p>
               Some plugins take a lot of optional configuration options. When a plugin takes many options, it is more
@@ -529,23 +540,20 @@ ListAppender list1 = ListAppender.createAppender("List1", true, false, null, nul
 ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build();
 ]]></pre>
           </subsection>
-        <subsection name="Custom ContextDataInjector">
+        <a name="ContextDataProvider"/>
+        <subsection name="Custom ContextDataProvider">
           <p>
-            The <code>ContextDataInjector</code> (introduced in Log4j 2.7) is responsible for
-            populating the LogEvent's
-            <a class="javadoc" href="../log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getContextData()">context data</a>
-            with key-value pairs or replacing it completely.
-            The default implementation is ThreadContextDataInjector, which obtains context attributes from the ThreadContext.
-          </p><p>
-          Applications may replace the default ContextDataInjector by setting the value of the system property
-          <tt>log4j2.contextDataInjector</tt> to the name of the custom ContextDataInjector class.
-        </p><p>
-          Implementors should be aware there are some subtleties related to thread-safety and implementing a
-          context data injector in a garbage-free manner.
-          See the <a class="javadoc" href="../log4j-core/apidocs/org/apache/logging/log4j/core/ContextDataInjector.html">ContextDataInjector</a>
-          javadoc for detail.
-        </p>
+            The <code>ContextDataProvider</code> (introduced in Log4j 2.13.2)
+            is an interface applications and libraries can use to inject additional key-value pairs into the LogEvent's
+            context data. Log4j's <code>ThreadContextDataInjector</code> uses <code>java.util.ServiceLoader</code>
+            to locate and load <code>ContextDataProvider</code> instances. Log4j itself adds the ThreadContextData to
+            the LogEvent using <code>org.apache.logging.log4j.core.impl.ThreadContextDataProvider</code>. Custom
+            implementations should implement the <code>org.apache.logging.log4j.core.util.ContextDataProvider</code>
+            interface and declare it as a service by defining the implmentation class in a file named
+            <code>META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider</code>.
+          </p>
         </subsection>
+        <a name="ThreadContextMap"/>
         <subsection name="Custom ThreadContextMap implementations">
           <p>
             A garbage-free StringMap-based context map can be installed by setting system property <tt>log4j2.garbagefreeThreadContextMap</tt>
@@ -559,7 +567,7 @@ ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLi
             method.
           </p>
         </subsection>
-          <subsection name="Custom Plugins">
+          <subsection name="Custom_Plugins">
             <p>See the <a href="plugins.html">Plugins</a> section of the manual.</p>
 <!-- TODO: some documentation here! -->
           </subsection>