Master Spring Boot Request Logging and Custom Filters in 4 Steps
This guide explains how to enable comprehensive request logging, use request/response wrappers, implement once‑per‑request filters, and define clean controller interfaces in Spring Boot 3.2.5, providing code examples and configuration tips for robust backend development.
Environment
Spring Boot version 3.2.5.
1. Record Request Data
Spring Boot offers a built‑in solution for logging payloads via AbstractRequestLoggingFilter . Subclasses should override beforeRequest() and afterRequest() to perform actual logging. Three concrete implementations exist:
CommonsRequestLoggingFilter
Log4jNestedDiagnosticContextFilter (deprecated)
ServletContextRequestLoggingFilter
Below is a typical configuration using CommonsRequestLoggingFilter :
<code>@Configuration
public class RequestLoggingConfig {
@Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
// Record query string
filter.setIncludeQueryString(true);
// Record request body
filter.setIncludePayload(true);
// Record headers
filter.setIncludeHeaders(true);
// Record client info
filter.setIncludeClientInfo(true);
// Prefix for log messages
filter.setAfterMessagePrefix("REQUEST DATA: ");
return filter;
}
}
</code>Enable debug logging for the filter:
<code>logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG</code>Example controller method:
<code>@PostMapping("")
public Article save(@RequestBody Article article) {
return article;
}
</code>When the endpoint is called, the console logs show all request details (see images).
GET requests are logged similarly (see image).
2. Request/Response Wrappers
To intercept and modify request or response bodies, Spring provides wrappers that can be used directly:
<code>@Component
public class ContentFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
byte[] responseBody = responseWrapper.getContentAsByteArray();
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(responseBody);
String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
responseWrapper.setHeader("X-API-SIGN", md5HashString);
// Must copy body back to the client
responseWrapper.copyBodyToResponse();
}
}
</code>The response now includes a signature header (see image).
3. Special Filter – OncePerRequestFilter
Filters can run before or after servlet execution. To ensure a filter runs only once per request, extend OncePerRequestFilter . This is especially important when internal forwards occur (Servlet 2.4+ no longer forwards through filters).
<code>@Component
public class AuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// TODO: authentication logic
filterChain.doFilter(request, response);
}
}
</code>Internal logic of OncePerRequestFilter determines whether the filter has already been applied (see image).
4. Controller Interface Definition
When multiple controller versions share the same method signatures, define a common interface to avoid duplication (DRY principle).
<code>@RequestMapping("") // optional annotation
public interface BookOperations {
@GetMapping("")
List<Book> getAll();
@GetMapping("/{id}")
Optional<Book> getById(@PathVariable int id);
@PostMapping("/save/{id}")
void save(@RequestBody Book book, @PathVariable int id);
}
</code>Implement the interface in a concrete controller:
<code>@RestController
@RequestMapping("/books")
public class BookController implements BookOperations {
private static final List<Book> BOOKS = List.of(
new Book("MySQL从删库到跑路", "李四"),
new Book("Java从入门到放弃", "王伟"),
new Book("JPA2从入门到精通", "李海")
);
public List<Book> getAll() { return BOOKS; }
public Optional<Book> getById(int id) { return Optional.of(new Book("MySQL从删库到跑路", "李四")); }
public void save(Book book, int id) { /* TODO */ }
}
</code>Annotate the class with @RestController or @Controller to inherit request‑mapping related annotations.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.