Careeroza
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