Real World Example

A Request-ID Filter for ShopEase

Support teams at companies like Swiggy and Paytm trace a complaint ("my order failed at 9:41") through the logs using a request id and the username. ShopEase adds its own filter to the security chain, right after authentication, so both are known:

package com.shopease.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.UUID;

public class RequestLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String requestId = UUID.randomUUID().toString().substring(0, 8);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String user = (auth == null) ? "anonymous" : auth.getName();

        MDC.put("requestId", requestId);
        MDC.put("user", user);
        response.setHeader("X-Request-Id", requestId);
        try {
            chain.doFilter(request, response);          // continue the chain
        } finally {
            MDC.clear();
        }
    }
}

Registering It in the Chain

@Bean
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
            .anyRequest().authenticated())
        .httpBasic(Customizer.withDefaults())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .csrf(csrf -> csrf.disable())
        // run after HTTP Basic has authenticated the user
        .addFilterAfter(new RequestLoggingFilter(), BasicAuthenticationFilter.class);
    return http.build();
}

Tip: Do not annotate a security filter with @Component: Spring Boot would also register it as a normal servlet filter and it would run twice. Create it with new inside the chain, or register a FilterRegistrationBean with setEnabled(false).

The Result in the Logs

Example

With logging.pattern.level=%5p [%X{requestId}] [%X{user}]:

curl -i -u asha:asha123 -X POST localhost:8080/api/orders -H "Content-Type: application/json" -d '{"items":[{"productId":1,"quantity":2}]}'

Example output

HTTP/1.1 201
X-Request-Id: 7c1e9a42
Content-Type: application/json

{"orderId":5012,"status":"PLACED","total":1598.0}

--- server log ---
 INFO [7c1e9a42] [asha] c.s.order.OrderService : Placing order with 1 item(s)
 INFO [7c1e9a42] [asha] c.s.order.OrderService : Order 5012 placed, total 1598.0