Java 如何在spring webflux WebFilter中正确使用slf4j MDC
我引用了这篇博文,但我不知道如何访问WebFilter中的reactor上下文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
@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是静态的,并在其中使用一个静态对象来存储值。这不是个问题吗?