Backend Development 7 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Boot Request Logging and Custom Filters in 4 Steps

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.

backendJavaFiltersRequest Loggingspring-boot
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.