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>