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 :

@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;
    }
}

Enable debug logging for the filter:

logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

Example controller method:

@PostMapping("")
public Article save(@RequestBody Article article) {
    return article;
}

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:

@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();
    }
}

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).

@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);
    }
}

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).

@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);
}

Implement the interface in a concrete controller:

@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 */ }
}

Annotate the class with @RestController or @Controller to inherit request‑mapping related annotations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.