1. Introduction

In this tutorial, we'll investigate how we can log incoming requests using Spring MVC.

2. Sample Application

Let's first look at our sample application.

@RestController
public class ContactController {

    @PostMapping("/contact/{name}")
    public String contact(@PathVariable("name") String name, @RequestBody String details) {
        return "Contact details received for: " + name;
    }
}

We have a simple REST controller with a single endpoint - /contact/{name}. In the upcoming examples, we'll make our requests against this endpoint.

3. Custom Implementations

Now, we'll explore some custom implementations to log incoming HTTP requests.

3.1. Using HandlerInterceptor

The HandlerInterceptor class provides us hooks to run before and after the request handling. Thus we will use these methods for logging request data:

@Slf4j
@Component
public class CustomRequestLoggingInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        StringBuffer requestURL = request.getRequestURL();
        log.info("preHandle => Request URL: {}", requestURL);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        int status = response.getStatus();
        log.info("afterCompletion => Response Status: {}", status);
    }
}

Our CustomRequestLoggingInterceptor class extends HandlerInterceptorAdapter which is an abstract class implementing HandlerInterceptor. Note that preHandle will run just before the request handling and afterCompletion will run after the request handling is finished.

Now that we have our HandlerInterceptor, we must register it using a WebMvcConfigurer bean:

@Component
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomRequestLoggingInterceptor());
    }
}

When the application gets a request, our CustomRequestLoggingInterceptor class also gets called:

INFO 27115 --- [nio-8081-exec-1] .j.s.m.r.CustomRequestLoggingInterceptor : preHandle => Request URL: http://localhost:8081/contact/John
INFO 27115 --- [nio-8081-exec-1] .j.s.m.r.CustomRequestLoggingInterceptor : afterCompletion => Response Status: 200

3.2. Using Filter

A filter runs before and after a servlet thus it is suitable for logging requests. In our case, the servlet is Spring MVC's DispatcherServlet which handles all incoming requests.

@Slf4j
@Component
public class CustomRequestLoggingFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
      throws IOException, ServletException {
        final HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
        final HttpServletResponse currentResponse = (HttpServletResponse) servletResponse;

        StringBuffer requestURL = currentRequest.getRequestURL();
        log.info("Request URL: {}", requestURL);
        try {
            chain.doFilter(currentRequest, servletResponse);
        } finally {
            int status = currentResponse.getStatus();
            log.info("Response status: {}", status);
        }
    }
}

Here, the actual request handling occurs with the chain.doFilter call. In other words, DispatcherServlet gets the request and selects the appropriate controller method. Thus, we're writing our log statements before and after this call in the filter.

Additionally, we can configure the registration properties of our filter using a FilterRegistrationBean, although we opted for the default values here.

When the application serves a request, the following logs are written:

INFO 5835 --- [nio-8081-exec-1] c.j.s.m.r.CustomRequestLoggingFilter     : Request URL: http://localhost:8081/contact/John
INFO 5835 --- [nio-8081-exec-1] c.j.s.m.r.CustomRequestLoggingFilter     : Response status: 200

4. Spring MVC Support

Spring MVC has built-in support for logging requests which we'll look at next.

4.1. Using CommonsRequestLoggingFilter

Spring MVC provides CommonsRequestLoggingFilter which can log request URL, body and other related information.

To use it in our application, we must first define a bean for it:

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter requestLoggingFilter = new CommonsRequestLoggingFilter();
    requestLoggingFilter.setIncludeClientInfo(true);
    requestLoggingFilter.setIncludeHeaders(true);
    requestLoggingFilter.setIncludeQueryString(true);
    requestLoggingFilter.setIncludePayload(true);
    return requestLoggingFilter;
}

Here, we're creating an instance of CommonsRequestLoggingFilter and enabling all include options for a rich log statement.

Then we'll change the log level of the filter to DEBUG. We can do it using the application.properties:

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

When a new request arrives, the filter provides the logs:

DEBUG 27115 --- [nio-8081-exec-1] o.s.w.f.CommonsRequestLoggingFilter      : Before request [uri=/contact/John;client=127.0.0.1;headers=[accept:"text/plain, application/json, application/*+json, */*", content-type:"text/plain;charset=UTF-8", user-agent:"Java/1.8.0_191", host:"localhost:8081", connection:"keep-alive", content-length:"15"]]
DEBUG 27115 --- [nio-8081-exec-1] o.s.w.f.CommonsRequestLoggingFilter      : After request [uri=/contact/John;client=127.0.0.1;headers=[accept:"text/plain, application/json, application/*+json, */*", content-type:"text/plain;charset=UTF-8", user-agent:"Java/1.8.0_191", host:"localhost:8081", connection:"keep-alive", content-length:"15"];payload=London, England]

4.2. Extending AbstractRequestLoggingFilter

Instead of using CommonsRequestLoggingFilter, we can also create our own filter extending the AbstractRequestLoggingFilter class. In fact CommonsRequestLoggingFilter is also an implementation of AbstractRequestLoggingFilter.

@Component
public class AnotherCustomLoggingFilter extends AbstractRequestLoggingFilter {

    @Value("${request.logging.shouldLog}")
    private boolean shouldLog;

    public AnotherCustomLoggingFilter(){
        setIncludeClientInfo(true);
        setIncludeHeaders(true);
        setIncludePayload(true);
        setIncludeQueryString(true);
        setBeforeMessagePrefix("Request started => ");
        setAfterMessagePrefix("Request ended => ");
    }

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        return shouldLog;
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }
}

Here, we have AnotherCustomLoggingFilter which implements 3 methods: shouldLog, beforeRequest and afterRequest.

We're enabling the logging behavior with an application property - request.logging.shouldLog. Inside the beforeRequest and afterRequest methods, we're simply logging the message though we could also perform more complex operations. Lastly, we're enabling the include options in the constructor.

The log output is very similar to CommonsRequestLoggingFilter:

INFO 5835 --- [nio-8081-exec-1] c.j.s.m.r.AnotherCustomLoggingFilter     : Request started => uri=/contact/John;client=127.0.0.1;headers=[accept:"text/plain, application/json, application/*+json, */*", content-type:"text/plain;charset=UTF-8", user-agent:"Java/1.8.0_191", host:"localhost:8081", connection:"keep-alive", content-length:"15"]
INFO 5835 --- [nio-8081-exec-1] c.j.s.m.r.AnotherCustomLoggingFilter     : Request ended => uri=/contact/John;client=127.0.0.1;headers=[accept:"text/plain, application/json, application/*+json, */*", content-type:"text/plain;charset=UTF-8", user-agent:"Java/1.8.0_191", host:"localhost:8081", connection:"keep-alive", content-length:"15"];payload=London, England

5. Summary

In this tutorial, we've looked at the different ways to log incoming requests using Spring MVC.

Check out the source code for all examples in this tutorial over on Github.