如何在CompletableFuture中保留slf4j MDC日志记录上下文?

时间:2018-03-05 12:40:40

标签: java slf4j completable-future mdc

执行异步org.slf4j.MDC时,父线程上下文以及MDC.put("fishid", randomId())上下文丢失。

这很糟糕,因为我正在使用某种“鱼标记”来跟踪多个日志文件中的一个请求的日志。

CompletableFutures

问题:如何在List<CompletableFuture<UpdateHotelAllotmentsRsp>> futures = tasks.stream() .map(task -> CompletableFuture.supplyAsync( () -> businesslogic(task)) .collect(Collectors.toList()); List results = futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); public void businesslogic(Task task) { LOGGER.info("mdc fishtag context is lost here"); } 的任务期间保留该ID?

{{1}}

4 个答案:

答案 0 :(得分:3)

最后,我创建了一个保留Supplier的{​​{1}}包装器。如果有人有更好的想法随时发表评论。

MDC

答案 1 :(得分:0)

是的,Twitter Future正确地做到了这一点。他们有一个Future.scala知道的Local.scala类。

该修补程序适用于Java作者,以解决此问题,以便您的本地状态在所有使用CompletableFutures的库中传播。基本上,Future使用Local.scala并在内部使用ThreadLocal直到.thenApply或.thenAccept为止,它将捕获状态并将其在需要时不停地转移到下一个状态。这适用于所有具有零第三方库更改的第三方库。

这里还有更多,但请戳Java作者来修复他们的东西... http://mail.openjdk.java.net/pipermail/core-libs-dev/2017-May/047867.html

直到那时,MDC永远都不会通过第三方库工作。

我对此的帖子 Does CompletableFuture have a corresponding Local context?

答案 2 :(得分:0)

我解决此问题的最可读的方法如下-

----------------- Thread utils类--------------------

SELECT
  DATE_FORMAT(created_at, '%M %Y') AS 'article',
  DATE_FORMAT(created_at, '%m')AS 'm',
  DATE_FORMAT(created_at,'%Y') AS 'y',
  COUNT(id) AS 'total'
FROM posts
GROUP BY DATE_FORMAT(created_at, '%Y%M')
ORDER BY m DESC

---------------用法--------------

$archives = DB::select("SELECT DATE_FORMAT(created_at, '%M %Y') AS 'article',DATE_FORMAT(created_at, '%m')AS 'm', DATE_FORMAT(created_at,'%Y') AS 'y', COUNT(id) AS 'total' FROM posts GROUP BY DATE_FORMAT(created_at, '%Y%M') ORDER BY m DESC");`

ThreadUtils中的WithMdc必须重载以包含CompletableFuture接受的其他功能接口

请注意,withMdc()方法是静态导入的,以提高可读性。

答案 3 :(得分:0)

我的解决方案主题是(它将与JDK 9+一起使用,因为从该版本开始就公开了几个可重写的方法)

  

让完整的生态系统了解MDC

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

  • 何时从此类中获得所有CompletableFuture的新实例?→我们需要返回一个MDC感知的相同版本。
  • 什么时候都能从此类之外获得CompletableFuture的新实例?→我们需要返回一个MDC感知的相同版本。
  • 在CompletableFuture类中使用哪个执行程序?→在所有情况下,我们需要确保所有执行程序都知道MDC

为此,我们通过扩展它来创建CompletableFuture的MDC感知版本类。我的版本如下所示

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;
                });
    }
}

MDCAwareForkJoinPool类看起来像

public class MDCAwareForkJoinPool extends ForkJoinPool {
    //Override constructors which you need

    @Override
    public <T> ForkJoinTask<T> submit(Callable<T> task) {
        return super.submit(MDCUtility.wrapWithMdcContext(task));
    }

    @Override
    public <T> ForkJoinTask<T> submit(Runnable task, T result) {
        return super.submit(wrapWithMdcContext(task), result);
    }

    @Override
    public ForkJoinTask<?> submit(Runnable task) {
        return super.submit(wrapWithMdcContext(task));
    }

    @Override
    public void execute(Runnable task) {
        super.execute(wrapWithMdcContext(task));
    }
}

要包装的实用方法例如

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 {
            return 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);
    }
}

以下是一些使用准则:

  • 使用类MDCAwareCompletableFuture而不是类CompletableFuture
  • CompletableFuture中的几个方法实例化自身版本,例如new CompletableFuture...。对于此类方法(大多数公共静态方法),请使用替代方法来获取MDCAwareCompletableFuture的实例。使用替代方法的一个例子可能是使用CompletableFuture.supplyAsync(...)
  • ,而不是使用new MDCAwareCompletableFuture<>().completeAsync(...)
  • 当您因为某个外部库而返回CompletableFuture实例而陷入困境时,使用方法MDCAwareCompletableFuturegetMDCAwareCompletionStage的实例转换为CompletableFutureMDCAwareForkJoinPool 。显然,您无法在该库中保留上下文,但是在您的代码触及应用程序代码后,此方法仍将保留上下文。
  • 在提供执行程序作为参数时,请确保它是MDC Aware,例如MDCAwareThreadPoolExecutor。您也可以通过覆盖execute方法来创建List<CompletableFuture<UpdateHotelAllotmentsRsp>> futures = tasks.stream() new MDCAwareCompletableFuture<UpdateHotelAllotmentsRsp>().completeAsync( () -> businesslogic(task)) .collect(Collectors.toList()); List results = futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); public UpdateHotelAllotmentsRsp businesslogic(Task task) { LOGGER.info("mdc fishtag context is not lost here"); } ,以服务您的用例。你明白了!

有了它,您的代码就会像

ClinicMap.js

您可以在post中以相同的方式找到以上所有内容的详细说明