Java 具有完整未来的MDC记录器

Java 具有完整未来的MDC记录器,java,spring-boot,slf4j,completable-future,mdc,Java,Spring Boot,Slf4j,Completable Future,Mdc,我使用的是MDC Logger,除了一个案例外,它对我来说非常适合。在我们使用CompletableFuture的代码中,对于创建的线程,MDC数据不会传递到下一个线程,因此日志会失败。例如,我在下面使用的代码片段中创建了新线程 CompletableFuture.runAsync(() -> getAcountDetails(user)); 日志的结果如下所示 2019-04-29 11:44:13,690 INFO | /app/rest/controller/userdetail

我使用的是MDC Logger,除了一个案例外,它对我来说非常适合。在我们使用CompletableFuture的代码中,对于创建的线程,MDC数据不会传递到下一个线程,因此日志会失败。例如,我在下面使用的代码片段中创建了新线程

CompletableFuture.runAsync(() -> getAcountDetails(user));
日志的结果如下所示

2019-04-29 11:44:13,690 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5] RestServiceExecutor:  service: 
2019-04-29 11:44:13,690 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5] RestServiceExecutor: 
2019-04-29 11:44:13,779 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5] UserDetailsRepoImpl: 
2019-04-29 11:44:13,950 INFO   [ForkJoinPool.commonPool-worker-3] RestServiceExecutor:  header: 
2019-04-29 11:44:13,950 INFO   [ForkJoinPool.commonPool-worker-3] RestServiceExecutor:  service: 
2019-04-29 11:44:14,012 INFO   [ForkJoinPool.commonPool-worker-3] CommonMasterDataServiceImpl: Cache: Retrieving Config Data details.
2019-04-29 11:44:14,028 INFO   [ForkJoinPool.commonPool-worker-3] CommonMasterDataServiceImpl: Cache: Retrieved Config Data details : 1
2019-04-29 11:44:14,028 INFO   [ForkJoinPool.commonPool-worker-3] CommonMasterDataServiceImpl: Cache: Retrieving Config Data details.
2019-04-29 11:44:14,033 INFO   [ForkJoinPool.commonPool-worker-3] CommonMasterDataServiceImpl: Cache: Retrieved Config Data details : 1
2019-04-29 11:44:14,147 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5] SecondaryCacheServiceImpl: Fetching from secondary cache
2019-04-29 11:44:14,715 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5] CommonMasterDataServiceImpl: Cache: Retrieving Config Data details.
2019-04-29 11:44:14,749 INFO  | /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |[http-nio-8182-exec-5]
下面是我的MDC数据,它不能通过线程
[ForkJoinPool.commonPool-worker-3]

| /app/rest/controller/userdetails | f80fdc1f-8123-3932-a405-dda2dc2a80d5 |
下面是我的logback.xml配置,其中sessionID是MDC数据

<configuration scan="true">
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>utf-8</charset>
            <Pattern>%d %-5level %X{sessionID} [%thread] %logger{0}: %msg%n</Pattern>
        </encoder>
    </appender>
</configuration>

这对TaskExecutor非常有效。但我还没有找到任何解决CompletableFuture的方法。

创建包装器方法

static CompletableFuture<Void> myMethod(Runnable runnable) {
    Map<String, String> previous = MDC.getCopyOfContextMap();
    return CompletableFuture.runAsync(() -> {
        MDC.setContextMap(previous);
        try {
            runnable.run();
        } finally {
            MDC.clear();
        }
    });
}
静态CompletableFuture myMethod(可运行可运行){
Map previous=MDC.getCopyOfContextMap();
返回CompletableFuture.runAsync(()->{
MDC.setContextMap(先前版本);
试一试{
runnable.run();
}最后{
MDC.clear();
}
});
}
并使用它而不是
CompletableFuture.runAsync

创建包装器方法

static CompletableFuture<Void> myMethod(Runnable runnable) {
    Map<String, String> previous = MDC.getCopyOfContextMap();
    return CompletableFuture.runAsync(() -> {
        MDC.setContextMap(previous);
        try {
            runnable.run();
        } finally {
            MDC.clear();
        }
    });
}
静态CompletableFuture myMethod(可运行可运行){
Map previous=MDC.getCopyOfContextMap();
返回CompletableFuture.runAsync(()->{
MDC.setContextMap(先前版本);
试一试{
runnable.run();
}最后{
MDC.clear();
}
});
}

使用它而不是CompletableFuture.runAsync

我的解决方案主题将是to(它将与JDK 9+一起工作,因为自该版本以来,公开了两个可重写的方法)

使整个生态系统了解MDC

为此,我们需要解决以下情况:

  • 我们什么时候才能从这个类中获得CompletableFuture的新实例?→ 我们需要返回一个支持MDC的版本
  • 我们什么时候才能从这个类之外获得CompletableFuture的新实例?→ 我们需要返回一个支持MDC的版本
  • 在CompletableFuture类中使用哪个执行器?→ 在任何情况下,我们都需要确保所有执行人都了解MDC
为此,让我们通过扩展来创建一个支持MDC的
CompletableFuture
版本类。我的版本如下所示

import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;

public class MDCAwareCompletableFuture<T> extends CompletableFuture<T> {

    public static final ExecutorService MDC_AWARE_ASYNC_POOL = new MDCAwareForkJoinPool();

    @Override
    public CompletableFuture newIncompleteFuture() {
        return new MDCAwareCompletableFuture();
    }

    @Override
    public Executor defaultExecutor() {
        return MDC_AWARE_ASYNC_POOL;
    }

    public static <T> CompletionStage<T> getMDCAwareCompletionStage(CompletableFuture<T> future) {
        return new MDCAwareCompletableFuture<>()
                .completeAsync(() -> null)
                .thenCombineAsync(future, (aVoid, value) -> value);
    }

    public static <T> CompletionStage<T> getMDCHandledCompletionStage(CompletableFuture<T> future,
                                                                Function<Throwable, T> throwableFunction) {
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return getMDCAwareCompletionStage(future)
                .handle((value, throwable) -> {
                    setMDCContext(contextMap);
                    if (throwable != null) {
                        return throwableFunction.apply(throwable);
                    }
                    return value;
                });
    }
}
要包装的实用方法如下

public static <T> Callable<T> wrapWithMdcContext(Callable<T> task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            return task.call();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static Runnable wrapWithMdcContext(Runnable task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            task.run();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static void setMDCContext(Map<String, String> contextMap) {
   MDC.clear();
   if (contextMap != null) {
       MDC.setContextMap(contextMap);
    }
}

我的解决方案主题是(它将与JDK 9+一起工作,因为自该版本以来,公开了两个可重写的方法)

使整个生态系统了解MDC

为此,我们需要解决以下情况:

  • 我们什么时候才能从这个类中获得CompletableFuture的新实例?→ 我们需要返回一个支持MDC的版本
  • 我们什么时候才能从这个类之外获得CompletableFuture的新实例?→ 我们需要返回一个支持MDC的版本
  • 在CompletableFuture类中使用哪个执行器?→ 在任何情况下,我们都需要确保所有执行人都了解MDC
为此,让我们通过扩展来创建一个支持MDC的
CompletableFuture
版本类。我的版本如下所示

import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;

public class MDCAwareCompletableFuture<T> extends CompletableFuture<T> {

    public static final ExecutorService MDC_AWARE_ASYNC_POOL = new MDCAwareForkJoinPool();

    @Override
    public CompletableFuture newIncompleteFuture() {
        return new MDCAwareCompletableFuture();
    }

    @Override
    public Executor defaultExecutor() {
        return MDC_AWARE_ASYNC_POOL;
    }

    public static <T> CompletionStage<T> getMDCAwareCompletionStage(CompletableFuture<T> future) {
        return new MDCAwareCompletableFuture<>()
                .completeAsync(() -> null)
                .thenCombineAsync(future, (aVoid, value) -> value);
    }

    public static <T> CompletionStage<T> getMDCHandledCompletionStage(CompletableFuture<T> future,
                                                                Function<Throwable, T> throwableFunction) {
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return getMDCAwareCompletionStage(future)
                .handle((value, throwable) -> {
                    setMDCContext(contextMap);
                    if (throwable != null) {
                        return throwableFunction.apply(throwable);
                    }
                    return value;
                });
    }
}
要包装的实用方法如下

public static <T> Callable<T> wrapWithMdcContext(Callable<T> task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            return task.call();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static Runnable wrapWithMdcContext(Runnable task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            task.run();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static void setMDCContext(Map<String, String> contextMap) {
   MDC.clear();
   if (contextMap != null) {
       MDC.setContextMap(contextMap);
    }
}

检查检查,但不要将其称为
myMethod
runAsyncWithMDC
或类似Hanks Talex,它应该可以工作,但问题是我们在多个地方使用completablefuture,我们可能需要在每个地方进行更新。对吗?因为您使用静态方法来运行异步调用,所以无法拦截它。所以你必须修改每个调用。但是不要调用它
myMethod
runAsyncWithMDC
或类似Hanks Talex,它应该可以工作,但问题是我们在多个地方使用completablefuture,我们可能需要在每个地方进行更新。对吗?因为您使用静态方法来运行异步调用,所以无法拦截它。所以你必须修改每个电话。