[JAVA] You use context to use MDC with Spring WebFlux

Incompatibility between WebFlux and MDC

With Spring Web MVC, which is commonly used in Spring, you will not encounter this problem.

The major difference between WebFlux and WebMVC is the handling of threads when processing request and response. Unlike Spring MVC, Event Loop Thread used in WebFlux handles one request in multiple threads.

However, the implementation of MDC depends on LocalThread. In other words, the thread that executed the MDC has the MDC information, but the thread that does not have the MDC information does not exist and does not appear in the log.

I've gone through this event at the last minute of the ** production release **, and here are the findings.

Pioneer

-[Examples of using Spring and WebFlux in the chat system of Line official account](https://speakerdeck.com/line_developers/examples-of-using-spring-and-webflux-in-the-chat-system-for-line -official-accounts)

The above two describe how to propagate MDC between multiple applications with WebFlux, which is one step more advanced than this theme. I would like to delve into this in separate article.

Main subject

The answer is posted in the next article

There is a function called context and three methods, logOnNext, logOnError, and put. By the way, the context here is the context of the Reactor. Please refer to this described in Reactor as a document.

Simply put, the context is a "** data can be propagated in a reactive environment **" mechanism that allows the MDC to function properly. (It seems that the subscription propagation mechanism is involved in data propagation, and I don't understand it until now, but the above document 9.8.2 has a similar explanation!)

As it is, the value as an article is not so much, so I will delve into each method.

What are you doing roughly

--In LogHelper.java,public static final String CONTEXT_MAP = "context-map";is defined. --logOnNext and logOnError refer to the context by the Key value of the constant CONTEXT_MAP. Since the context has an I / F like Map, it picks up the Value. It thrusts it into the MDC, outputs a log, and clears the MDC when it finishes operating. --put is writing to the context with the constant CONTEXT_MAP.

logOnNext (MDC outputs a valid log when normal)

LogHelper.java


  public static <T> Consumer<Signal<T>> logOnNext(
    Consumer<T> log) {
    return signal -> {
      if (signal.getType() != SignalType.ON_NEXT) return;
      Optional<Map<String, String>> maybeContextMap
        = signal.getContext().getOrEmpty(CONTEXT_MAP);
      if (maybeContextMap.isEmpty()) {
        log.accept(signal.get());
      } else {
        MDC.setContextMap(maybeContextMap.get());
        try {
          log.accept(signal.get());
        } finally {
          MDC.clear();
        }
      }
    };

logOnNext is used in combination with WebFlux's doOnEach method. signal.getType ()! = SignalType.ON_NEXT is the miso, and this method does not respond when calling an abnormal system. It ignites and logs when the data propagates normally.

logOnError (MDC outputs a valid log when abnormal)

LogHelper.java


  public static <T> Consumer<Signal<T>> logOnError(
    Consumer<Throwable> log) {
    return signal -> {
      if (!signal.isOnError()) return;
      Optional<Map<String, String>> maybeContextMap
        = signal.getContext().getOrEmpty(CONTEXT_MAP);
      if (maybeContextMap.isEmpty()) {
        log.accept(signal.getThrowable());
      } else {
        MDC.setContextMap(maybeContextMap.get());
        try {
          log.accept(signal.getThrowable());
        } finally {
          MDC.clear();
        }
      }
    };
  }

The outline of logOnError is not much different from that of logOnNext. However, the signal it references has changed, and it logs in response to the error signal. In other words, if you use logOnXXX at the end of a method, Error will not be called if it ends normally, and Next will not be called if it ends abnormally.

put (write MDC to context)

LogHelper.java


  public static Function<Context, Context> put(String key, String value) {
    return ctx -> {
      Optional<Map<String, String>> maybeContextMap =
        ctx.getOrEmpty(CONTEXT_MAP);
      if (maybeContextMap.isPresent()) {
        maybeContextMap.get().put(key, value);
        return ctx;
      } else {
        Map<String, String> ctxMap = new HashMap<>();
        ctxMap.put(key, value);
        return ctx.put(CONTEXT_MAP, ctxMap);
      }
    };

The role of put is straightforward, it just stores the data in context. The usage method is as follows.

.subscriberContext(put("hogehogeId", response.getHogehogeId().toString()))

subscriberContext is a series of processes as far as the document is seen. It seems to generate the context in. put picks up and adds the constant CONTEXT and returns a new context, so you can endlessly plunge values into the MDC within the chain of data propagation.

end

Is it impossible to use MDC in a reactive environment in the first place? Please let me know if there is a good way.

Recommended Posts

You use context to use MDC with Spring WebFlux
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to use built-in h2db with spring boot
If you want to use Mockito with Kotlin, use mockito-kotlin
Use Spring JDBC with Spring Boot
You are required to use winpty with docker exec [Windows]
How to use Lombok in Spring
Use Basic Authentication with Spring Boot
How to use Spring Data JDBC
How to use mssql-tools with alpine
How to use ModelMapper (Spring boot)
Beginning with Spring Boot 0. Use Spring CLI
It seems that MDC can be propagated between applications by using context together with Spring WebFlux.
If you dare to compare Integer with "==" ...
How to use BootStrap with Play Framework
I want to use DBViewer with Eclipse 2018-12! !!
Use Spring Security JSP tags with FreeMarker
Use cache with EhCashe 2.x with Spring Boot
[Java] Why do you bother to use the interface (Spring is also available)
[Spring Boot] If you use Spring Boot, it was convenient to use a lot of util.
With Tomcat you can use placeholders ($ {...}) in web.xml
How to use Java framework with AWS Lambda! ??
How to use Spring Boot session attributes (@SessionAttributes)
I want to use java8 forEach with index
Try to implement login function with Spring Boot
When you want to use the method outside
How to use Java API with lambda expression
Super easy way to use enum with JSP
How to use nfs protocol version 2 with ubuntu 18.04
[JAVA] [Spring] [MyBatis] Use IN () with SQL Builder
How to use docker compose with NVIDIA Jetson
Try to automate migration with Spring Boot Flyway
[Java] Article to add validation with Spring Boot 2.3.1.
I wanted to gradle spring boot with multi-project
Try to display hello world with spring + gradle
How to use nginx-ingress-controller with Docker for Mac
What do you use when converting to String?
[Introduction to Spring Boot] Authentication function with Spring Security
If you use SQLite with VSCode, use the extension (how to see the binary file of sqlite3)
Short hand to read JSON body from ServerHttpRequest with self-made WebFilter etc. with Spring WebFlux
Code to use when you want to process Json with only standard library in Java
Settings for connecting to MySQL with Spring Boot + Spring JDBC
I tried to implement file upload with Spring MVC
Automatically map DTOs to entities with Spring Boot API
If you want to separate Spring Boot + Thymeleaf processing
How to use Struts2 * Spring Framework (Spring plugin) June 2017 Version
[Java] How to omit spring constructor injection with Lombok
How to use Oracle JDK 9 EA with Travis CI
Considering a property editor to use with SpringToolSuite (STS)
How to use Z3 library in Scala with Eclipse
I tried to get started with Spring Data JPA
How to use CommandLineRunner in Spring Batch of Spring Boot
Until you start development with Spring Boot in eclipse 1
How to use JDD library in Scala with Eclipse
How to use RealSense with ubuntu 20.04 and ROS Noetic
How to boot by environment with Spring Boot of Maven
Until you start development with Spring Boot in eclipse 2
You can't (yet) pass arguments to buildkit with docker-compose
How to use In-Memory Job repository in Spring Batch
Attempt to SSR Vue.js with Spring Boot and GraalJS
Try to work with Keycloak using Spring Security SAML (Spring 5)