Java 如何在spring webflux WebFilter中正确使用slf4j MDC

Java 如何在spring webflux WebFilter中正确使用slf4j MDC,java,spring-webflux,project-reactor,Java,Spring Webflux,Project Reactor,我引用了这篇博文,但我不知道如何访问WebFilter中的reactor上下文 @Component public class RequestIdFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { List<String> myHeader = exchan

我引用了这篇博文,但我不知道如何访问WebFilter中的reactor上下文

@Component
public class RequestIdFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        List<String> myHeader =  exchange.getRequest().getHeaders().get("X-My-Header");

        if (myHeader != null && !myHeader.isEmpty()) {
            MDC.put("myHeader", myHeader.get(0));
        }

        return chain.filter(exchange);
    }
}
@组件
公共类RequestIdFilter实现WebFilter{
@凌驾
公共Mono筛选器(服务器WebExchange exchange、WebFilterChain链){
List myHeader=exchange.getRequest().getHeaders().get(“X-My-Header”);
if(myHeader!=null&&!myHeader.isEmpty()){
put(“myHeader”,myHeader.get(0));
}
返回链。过滤器(交换);
}
}

您可以执行与下面类似的操作,您可以使用您喜欢的任何类设置
上下文
,在本例中,我刚刚使用了标题,但是自定义类就可以了。 如果在此处设置,则使用处理程序等进行的任何日志记录也将有权访问
上下文

下面的
logWithContext
设置MDC并在之后清除它。显然,这可以被你喜欢的任何东西所取代

public class RequestIdFilter  implements WebFilter {

    private Logger LOG = LoggerFactory.getLogger(RequestIdFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        return chain.filter(exchange)
                .doAfterSuccessOrError((r, t) -> logWithContext(headers, httpHeaders -> LOG.info("Some message with MDC set")))
                .subscriberContext(Context.of(HttpHeaders.class, headers));
    }

    static void logWithContext(HttpHeaders headers, Consumer<HttpHeaders> logAction) {
        try {
            headers.forEach((name, values) -> MDC.put(name, values.get(0)));
            logAction.accept(headers);
        } finally {
            headers.keySet().forEach(MDC::remove);
        }

    }

}
公共类RequestIdFilter实现WebFilter{
私有记录器LOG=LoggerFactory.getLogger(RequestIdFilter.class);
@凌驾
公共Mono筛选器(服务器WebExchange exchange、WebFilterChain链){
HttpHeaders=exchange.getRequest().getHeaders();
返回链。过滤器(交换)
.doAfterSuccessOrError((r,t)->logWithContext(headers,httpHeaders->LOG.info(“设置了MDC的某些消息”))
.subscriberContext(Context.of(HttpHeaders.class,headers));
}
静态void logWithContext(HttpHeaders、Consumer logAction){
试一试{
headers.forEach((名称,值)->MDC.put(名称,值.get(0));
logAction.accept(标题);
}最后{
headers.keySet().forEach(MDC::remove);
}
}
}

以下是一个基于最新方法的解决方案,截至2021年5月,该方法取自:

import java.util.List;
导入java.util.Optional;
导入java.util.UUID;
导入java.util.function.Consumer;
导入lombok.extern.slf4j.slf4j;
导入org.slf4j.MDC;
导入org.springframework.context.annotation.Configuration;
导入org.springframework.http.HttpHeaders;
导入org.springframework.http.server.reactive.ServerHttpRequest;
导入org.springframework.web.server.ServerWebExchange;
导入org.springframework.web.server.WebFilter;
导入org.springframework.web.server.WebFilterChain;
导入reactor.core.publisher.Mono;
导入reactor.core.publisher.Signal;
导入reactor.util.context.context;
@Slf4j
@配置
公共类RequestIdFilter实现WebFilter{
@凌驾
公共Mono筛选器(服务器WebExchange exchange、WebFilterChain链){
ServerHttpRequest请求=exchange.getRequest();
String requestId=getRequestId(request.getHeaders());
返回链
.过滤器(交换)
.doOnEach(logOnEach(r->log.info(“{}{}”,request.getMethod(),request.getURI()))
.contextWrite(Context.of(“Context_KEY”,requestId));
}
私有字符串getRequestId(HttpHeaders){
List requestIdHeaders=headers.get(“X-Request-ID”);
return requestIdHeaders==null | | requestIdHeaders.isEmpty()
?UUID.randomUUID().toString()
:requestIdHeaders.get(0);
}
公共静态消费者登录(消费者登录声明){
返回信号->{
String contextValue=signal.getContextView().get(“CONTEXT_KEY”);
try(MDC.MDCCloseable cMdc=MDC.putCloseable(“MDC_KEY”,contextValue)){
logStatement.accept(signal.get());
}
};
}
公共静态消费者登录下一页(消费者登录声明){
返回信号->{
如果(!signal.isOnNext())返回;
String contextValue=signal.getContextView().get(“CONTEXT_KEY”);
try(MDC.MDCCloseable cMdc=MDC.putCloseable(“MDC_KEY”,contextValue)){
logStatement.accept(signal.get());
}
};
}
}
假设您的
应用程序中有以下行。属性

logging.pattern.level=[%X{MDC_KEY}] %5p
然后,每次调用端点时,您的服务器日志将包含如下日志:

2021-05-06 17:07:41.852 [60b38305-7005-4a05-bac7-ab2636e74d94]  INFO 20158 --- [or-http-epoll-6] my.package.RequestIdFilter    : GET http://localhost:12345/my-endpoint/444444/
每次您想要在反应式上下文中手动记录某些内容时,您必须将以下内容添加到反应式链中:

.doOnEach(logOnNext(r -> log.info("Something")))
如果希望将
X-Request-ID
传播到其他服务以进行分布式跟踪,则需要从反应式上下文(而不是从MDC)中读取它,并使用以下内容包装
WebClient
代码:

Mono.deferContextual(
    ctx -> {
      RequestHeadersSpec<?> request = webClient.get().uri(uri);
      request = request.header("X-Request-ID", ctx.get("CONTEXT_KEY"));
      // The rest of your request logic...
    });
Mono.com(
ctx->{
RequestHeadersSpec request=webClient.get().uri(uri);
request=request.header(“X-request-ID”,ctx.get(“CONTEXT_KEY”);
//其余的请求逻辑。。。
});

您需要反转
.subscriberContext
.doAfterSuccessOrError
但是,因为上下文写入仅对ITI之上的运算符可见。如果我们要为整个应用程序设置上下文,我们该如何做呢?@SimonBaslé,我使用的是reactor的.log()。使用.log()记录事件时,MDC数据将丢失。不管怎样,要把MDC干净利落地送到那里?不。您必须了解,在同一个线程上可能会发生多个不同的处理的世界中,MDC不再是一个银弹。put是静态的,并在其中使用一个静态对象来存储值。这不是个问题吗?