All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.util.mdc.MDCUtils Maven / Gradle / Ivy

The newest version!
package org.pepsoft.util.mdc;

import org.slf4j.MDC;

import java.util.*;
import java.util.concurrent.Callable;

import static java.util.stream.Collectors.toMap;

/**
 * Utility methods for working with the classes and interfaces in this package.
 */
public final class MDCUtils {
    /**
     * Finds all MDC context information in the causal chain and combines them
     * into one map. If a key occurs more than once with different values, the
     * values are concatenated together, separated by commas.
     *
     * @param exception The exception for which to gather the MDC context
     *                  information.
     * @return All MDC context information from the causal chain combined
     * together. May be an empty {@code Map}, but never {@code null}.
     */
    public static Map gatherMdcContext(Throwable exception) {
        Map> mdcContext = new HashMap<>();
        do {
            Optional.of(exception)
                    .filter(e -> e instanceof MDCContextProvider)
                    .map(e -> ((MDCContextProvider) e).getMdcContext())
                    .ifPresent(context -> context.forEach((key, value)
                            -> mdcContext.computeIfAbsent(key, k -> new HashSet<>()).add(value)));
            exception = exception.getCause();
        } while (exception != null);
        return mdcContext.entrySet().stream()
                .collect(toMap(Map.Entry::getKey, entry -> String.join(",", entry.getValue())));
    }

    /**
     * Execute a task returning a result with a set of key-value pairs on the {@link MDC} log context, wrapping any
     * exception thrown from the task in a {@link MDCCapturingException} to capture the full current MDC context if
     * necessary.
     *
     * @param task    The task to execute.
     * @param context An interleaved list of keys and values. Must have an even length. The keys must be
     *                {@code String}s. The values may be any type and will be converted to a string by invoking
     *                {@code toString()}. They may also be {@code null}, in which case the string {@code "null"} will be
     *                placed on the context.
     * @return The result of the task.
     */
    public static  T doWithMdcContext(Callable task, Object... context) {
        final Set keys = new HashSet<>();
        for (int i = 0; i < context.length; i += 2) {
            final String key = (String) context[i];
            final String value = String.valueOf(context[i + 1]);
            keys.add(key);
            MDC.put(key, value);
        }
        try {
            return task.call();
        } catch (Exception e) {
            // Check if the MDC context has already been captured
            boolean found = false;
            Throwable exception = e;
            do {
                if (exception instanceof MDCContextProvider) {
                    found = true;
                    break;
                }
                exception = exception.getCause();
            } while (exception != null);
            if (found) {
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else {
                    throw new RuntimeException(e);
                }
            } else {
                throw new MDCWrappingRuntimeException(e);
            }
        } finally {
            for (String key: keys) {
                MDC.remove(key);
            }
        }
    }

    /**
     * Checks whether the MDC context has been captured somewhere on the causal chain already, and if not, wraps the
     * exception in an {@link MDCCapturingException} or {@link MDCCapturingRuntimeException}.
     *
     * @param exception The exception to check for the presence of a captured MDC context.
     * @return An exception that is guaranteed to have the MDC context of the current thread somewhere on the chain.
     */
    public static Throwable decorateWithMdcContext(Throwable exception) {
        Throwable cause = exception;
        do {
            if (cause instanceof MDCContextProvider) {
                return exception;
            }
            cause = cause.getCause();
        } while (cause != null);
        if (exception instanceof RuntimeException) {
            return new MDCWrappingRuntimeException(exception);
        } else {
            return new MDCWrappingException(exception);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy