Skip to content

Scoped context #2438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move ContextDataProviders to the API
  • Loading branch information
rgoers committed Apr 4, 2024
commit 44b19b0e8a2a8f2c30fdd1a72a238c13ed306bf4
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@
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;
Expand Down Expand Up @@ -82,11 +81,8 @@ protected void log(
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@ public static void beforeAll() {
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();
Expand Down
106 changes: 106 additions & 0 deletions log4j-api/src/main/java/org/apache/logging/log4j/ContextData.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@
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.
Expand Down Expand Up @@ -137,7 +136,7 @@ public ResourceLoggerBuilder withMessageFactory(MessageFactory messageFactory) {
* 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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;

/**
Expand Down Expand Up @@ -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()) {
Expand All @@ -69,13 +69,23 @@ public static Map<String, Renderable> getContextMap() {
}

/**
* 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) {
Expand All @@ -85,6 +95,36 @@ public static <T> T get(String key) {
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.
*
Expand Down Expand Up @@ -316,7 +356,7 @@ public static <R> R callWhere(Map<String, ?> map, Callable<R> op) throws Excepti
* @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 {
Expand Down Expand Up @@ -460,11 +500,11 @@ public void run() {
if (contextStack != null) {
ThreadContext.setStack(contextStack);
}
ScopedContextAnchor.addScopedContext(scopedContext);
ScopedContextDataProvider.addScopedContext(scopedContext);
try {
op.run();
} finally {
ScopedContextAnchor.removeScopedContext();
ScopedContextDataProvider.removeScopedContext();
ThreadContext.clearAll();
}
}
Expand Down Expand Up @@ -511,18 +551,22 @@ public R call() throws Exception {
if (contextStack != null) {
ThreadContext.setStack(contextStack);
}
ScopedContextAnchor.addScopedContext(scopedContext);
ScopedContextDataProvider.addScopedContext(scopedContext);
try {
return op.call();
} finally {
ScopedContextAnchor.removeScopedContext();
ScopedContextDataProvider.removeScopedContext();
ThreadContext.clearAll();
}
}
}

/**
* 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 {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@
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;
Expand Down Expand Up @@ -296,8 +295,8 @@ public void logMessage(
}
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.spi;

import java.util.Map;
import org.apache.logging.log4j.util.StringMap;

/**
* Source of context data to be added to each log event.
*/
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>
* 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();

/**
* 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 void addAll(StringMap map) {
Map<String, String> contextMap = supplyContextData();
if (contextMap != null) {
contextMap.forEach(map::putValue);
}
}
}
Loading