5
votes

Spring boot 2.1.5 Project Reactor 3.2.9

In my webflux project I extensively use the reactor contexts in order to pass around some values.

I set up a filter and am trying to log things which are in the context and to log different things in case of error/success.

I have checked this documentation: https://projectreactor.io/docs/core/release/reference/#context

I still struggle (especially on the error side) to get it.

Basically, I have this filter:

@Component
public class MdcWebFilter implements WebFilter {

    @NotNull
    @Override
    public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange,
                             WebFilterChain webFilterChain) {

        Mono<Void> filter = webFilterChain.filter(serverWebExchange);

        return filter
            .doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
                @Override
                public void accept(Void aVoid, Throwable throwable) {
                    //Here i would like to be able to access to the request's context
                    System.out.println("doAfterSuccessOrError:" + (throwable==null ? "OK" : throwable.getMessage())+"log the context");
                }
            })
            .doOnEach(new Consumer<Signal<Void>>() {
                @Override
                public void accept(Signal<Void> voidSignal) {
                    //Here i have the context but i don't really know if i am in success or error
                    System.out.println("doOnEach:"+"Log OK/KO and the exception" + voidSignal.getContext());
                }
            })
            .subscriberContext(context -> context.put("somevar", "whatever"));
    }

}

I also tried with a flatMap() and a Mono.subscriberContext() but i am not sure how to plug correctly with the filter (especially in error).

What would be the best way to achieve this ?

2
there are cleaner solutions but i would like to know what it is you want to log?Toerktumlare
Business stuff.... the number of rows from a query, modified ids.... The client id that we received from the caller... Some kind of correlation id generated at the beginning of the request and passed along the entire flow to know which log relates to which query... Etc etc Sometimes we also wish to log everything once at the end of the request instead of many times during the execution.Arnaud Villevieille
simonbasle.github.io/2018/02/… Is how you use the MDC and the contextToerktumlare
I know this article. I use it and it functions well. Though, the main problem here is how to get the context in error status.Arnaud Villevieille
To be more precise, to get the context in error mode, know that you are in error mode and have the exception.Arnaud Villevieille

2 Answers

2
votes

I know this is probably not the cleanest of the solutions, but you could create a container class that would keep the context between your two callbacks.

You would store the context at doOnEach and then you would be able to load it back at doAfterSuccessOrError:

    public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {

        @lombok.Data
        class MyContextContainer {
            private Context context;
        }

        MyContextContainer container = new MyContextContainer();

        Mono<Void> filter = webFilterChain.filter(serverWebExchange);

        return filter
            .doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
                @Override
                public void accept(Void aVoid, Throwable throwable) {
                    // load the context here
                    Context context = container.getContext();
                    // then do your stuff here
                }
            })
            .doOnEach(new Consumer<Signal<Void>>() {
                @Override
                public void accept(Signal<Void> voidSignal) {
                    // store the context here
                    container.setContext(voidSignal.getContext());
                }
            })
            .subscriberContext(context -> context.put("somevar", "whatever"));
    }

It doesn't need to be a class, really. It could be an AtomicReference, but you get the idea.

Again, this might be just a workaround. I believe there must be a better way to access the context.

2
votes

I'm not sure whether it possible access request reactor context from within WebFilter. WebFilter context exists in another Mono chain. But it is do possible to assosiate attributes with request and able to fetch these attributes during request life time RequestContextHolder for Reactive Web Very similar to Servlet API.

Controller:

@GetMapping(path = "/v1/customers/{customerId}")
public Mono<Customer> getCustomerById(
    @PathVariable("customerId") String customerId,
    ServerWebExchange serverWebExchange)
{
  serverWebExchange.getAttributes().put("traceId", "your_trace_id");
  return customerService.findById(customerId);
}

WebFilter:

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    // ...
    String traceId = exchange.getAttributeOrDefault("traceId", "default_value_goes_here");
    //...
    return chain.filter(exchange);
}