MDC in Spring MVC

Logging with Mapped Diagnostic Context (MDC) to provide a way to enrich log messages with pieces of information that can be useful for better tracking program execution.

In this post, we will see how to include basic MDC, such as request information or the authenticated user id, with Spring MVC.

Logback provides a useful class MDCInsertingServletFilter which is a filter that will add the essential HTTP request information.

You can configure a spring-boot to include this filter like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package lin.louis.application.config;

import ch.qos.logback.classic.helpers.MDCInsertingServletFilter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MonitoringConfig {

    @Bean
        FilterRegistrationBean<MDCInsertingServletFilter> mdcFilterRegistrationBean() {
        FilterRegistrationBean<MDCInsertingServletFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new MDCInsertingServletFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(Integer.MIN_VALUE);
        return registrationBean;
    }
}

If you are using spring-security, you may also include some context about the user in the MDC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MDCAuthenticationFilter extends OncePerRequestFilter {

    private static final String USER = "user";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain
    ) throws ServletException, IOException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof UserDetails) {
            UserDetail user = (UserDetails) authentication.getPrincipal();
            MDC.put(USER, user.getUsername());
            try {
                filterChain.doFilter(request, response);
            } finally {
                MDC.remove(USER);
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

And to add this filter in your spring-security configuration, you need to add after your authentication filter. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final RequestMatcher AUTHENTICATED_URLS = new OrRequestMatcher(
            new AntPathRequestMatcher("/api/**"),
    );

    private static final String PUBLIC_URL = "/actuator/**";

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        //@formatter:off
        http
                .cors()
            .and()
                .sessionManagement().sessionCreationPolicy(STATELESS)
            .and()
                .exceptionHandling()
                .defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AUTHENTICATED_URLS)
            .and()
                .authenticationProvider(this.customAuthenticationProvider())
                .addFilterBefore(this.customAuthenticationFilter(), AnonymousAuthenticationFilter.class)
                .addFilterAfter(new MDCAuthenticationFilter(), CustomAuthenticationFilter.class)
                .authorizeRequests()
                .requestMatchers(AUTHENTICATED_URLS).authenticated()
                .antMatchers(PUBLIC_URL).permitAll()
            .and()
                .csrf().disable()
                .formLogin().disable()
                .logout().disable();
        //@formatter:on
    }

    // replace with your authentication provider
    private CustomAuthenticationProvider customAuthenticationProvider() {
      return new CustomAuthenticationProvider();
    }

    // replace with your authentication filter
    private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        final CustomAuthenticationFilter filter = new CustomAuthenticationFilter(AUTHENTICATED_URLS);
        filter.setAuthenticationManager(this.authenticationManager());
        return filter;
    }
}