Spring Boot ErrorController のログに Logback MDC の情報を付与したい


([Stack Overflowとのマルチポスト](https://stackoverflow.com/q/66865224/4506703)になります)

Spring Boot の ログ出力情報としてリクエスト単位で固有の値を共通的に付与したいと考えています。  
この目的のため、Logback
MDCを利用し[リファレンスの例7.5](http://logback.qos.ch/manual/mdc_ja.html#autoMDC)を参考にして後述のように実装しました。

この実装では、Spring
MVCの通常のcontrollerなどでは想定通り所望の情報(今回の実装では`requestId`の値)が付与されてログ出力されるのですが、
[`ErrorContoller`](https://docs.spring.io/spring-
boot/docs/2.4.4/reference/htmlsingle/#boot-features-error-handling)
実装クラスのログ出力では付与されませんでした。

`ErrorController`に実装した処理が実行されるのは`MDCFilter`を抜けてからだ(`MDC.remove()`が実行された後だからだ)、という原因は理解できるのですが、ではどのように実装すれば良いのか、というのがわかりません。

(`DispatcherServlet`よりも先に`MDCFilter`が適用されるようにすれば良いように思われたのですが、その実現方法がわかりませんでした。)

[code]

    @Controller
    @RequestMapping("/error")
    @RequiredArgsConstructor
    @Slf4j
    public class MyErrorController implements ErrorController {
    
        private final ErrorAttributes errorAttributes;
    
        @Override
        public String getErrorPath() {
            return null;
        }
    
        @RequestMapping
        @ResponseBody
        public Map<String, String> error(final HttpServletRequest request) {
            final ServletWebRequest webRequest = new ServletWebRequest(request);
            final Throwable th = errorAttributes.getError(webRequest);
    
            if (th != null) {
                // ここで出力したログにMDCの値(requestId)が付与されない
                log.error("MyErrorController", th);
            }
    
            return Map.of("result", "error");
        }
    }
    
[/code]

[code]

    // http://logback.qos.ch/manual/mdc.html#autoMDC
    public class MDCFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
            final FilterChain filterChain)
            throws ServletException, IOException {
    
            final String requestId = UUID.randomUUID().toString();
    
            MDC.put("requestId", requestId);
    
            try {
                filterChain.doFilter(request, response);
            } finally {
                MDC.remove("requestId");
            }
        }
    
    }
    
[/code]

[code]

    @Configuration
    public class MyConfig {
    
        @Bean
        public FilterRegistrationBean<MDCFilter> mdcFilter() {
            final FilterRegistrationBean<MDCFilter> bean = new FilterRegistrationBean<>();
            bean.setFilter(new MDCFilter());
            bean.addUrlPatterns("/*");
            bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return bean;
        }
    }
    
[/code]

`logback-spring.xml`:

[code]

    <configuration>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] requestId:%X{requestId} %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <root level="debug">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    
[/code]

実行結果("`requestId:`" の後ろにUUIDが出力されて欲しいが、そうなっていない):

[code]

    18:15:13.705 [http-nio-8080-exec-1] requestId: ERROR c.e.l.MyErrorController - MyErrorController
    java.lang.RuntimeException: Error occured.
    ...
    
[/code]

([完全なコードはこちら](https://github.com/yukihane/logging-with-mdc-demo))

* * *

類似の質問として次のようなものを見つけました(が、明確な回答は寄せられていないようでした):

  * [Preserve custom MDC attributes during exception-handling in Spring Boot](https://stackoverflow.com/q/55146885/4506703) \- Stack Overflow
  * [How can I make the MDC available during logging in a spring controller advice?](https://stackoverflow.com/q/44257548/4506703) \- Stack Overflow




「`remove`せず放置する」というのも対策のひとつとして考えられるかと思います。

有効でなくなった情報を保持したままにする、というのは確かにあまり行儀は良くないですが、実際のところ、今回ユーザリクエストを処理したスレッドは近い将来また別のユーザリクエストを処理するために再利用されるでしょう。  
そのタイミングで有効な情報で上書きされるため、実害はまず発生しないと思われます。

リクエストの都度MDCをクリアする必要があるかの質問はこちらにありました。検討の参考になるかもしれません:

  * [Do we need clear MDC after HTTP request in Spring](https://stackoverflow.com/q/42414643/4506703) \- Stack Overflow

