To use MDC with Spring WebFlux, use context I wrote about the story of propagating MDC between applications. I will. Knowledge of the above article is required in terms of content.
-Examples of using Spring and WebFlux in the chat system of Line official account ――How can I get request information on MDC? The topic. Patterns to solve using WebFilter, Hook, Context
Microservices, which are popular these days, come with a lot of complexity, one of which is logs. There is a natural demand for the communication that flows from A system-> B system-> C system to be made into a single log later, and various methods have been taken to solve this. (Spring Sleuth, etc.)
In the same way, there was a demand to let us take over various debug information together, and our system was just caught up in this.
This time, I will take up one of the methods introduced in Passing Context with Spring Web Flux
.
To briefly describe what WebFilter is doing, which I will introduce from now on
--For the request, I got all the HttpHeaders with the prefix X-MDC-
, stripped the prefix and put it in the context. The key value of the context is the constant CONTEXT_MAP
.
--For the response, the value taken from the constant CONTEXT_MAP
is returned with the prefix X-MDC-
.
MdcHeaderFilter.java
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import java.util.Map;
import static java.util.stream.Collectors.toMap;
import static jp.co.example.helper.LogHelper.*;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MdcHeaderFilter implements WebFilter {
private static final String MDC_HEADER_PREFIX = "X-MDC-";
@Override
public Mono<Void> filter(
ServerWebExchange ex,
WebFilterChain chain) {
ex.getResponse().beforeCommit(
() -> addContextToHttpResponseHeaders(ex.getResponse())
);
return chain.filter(ex)
.subscriberContext(
ctx -> addRequestHeadersToContext(ex.getRequest(), ctx)
);
}
private Context addRequestHeadersToContext(
final ServerHttpRequest request,
final Context context) {
final Map<String, String> contextMap = request
.getHeaders().toSingleValueMap().entrySet()
.stream()
.filter(x -> x.getKey().startsWith(MDC_HEADER_PREFIX))
.collect(
toMap(v -> v.getKey().substring(MDC_HEADER_PREFIX.length()),
Map.Entry::getValue
)
);
//For example, if you want to put a cookie, do this
String cookie = request.getCookies().containsKey("EXAMPLE") ?
request.getCookies().getFirst("EXAMPLE").getValue() : "none";
contextMap.put("example-cookie", cookie);
return context.put(CONTEXT_MAP, contextMap);
}
private Mono<Void> addContextToHttpResponseHeaders(
final ServerHttpResponse res) {
return Mono.subscriberContext().doOnNext(ctx -> {
if (!ctx.hasKey(CONTEXT_MAP)) return;
final HttpHeaders headers = res.getHeaders();
ctx.<Map<String, String>>get(CONTEXT_MAP).forEach(
(key, value) -> headers.add(MDC_HEADER_PREFIX + key, value)
);
}).then();
}
}
It's very simple to do, but it's versatile. It can also be used when you have to use a cookie as user information for debugging like our ~~ F ● ck ~~ system.
Don't get caught up in the response you get back to your customers!