Log Incoming Requests In Spring

How to Log Incoming Requests In Spring

In this post, we will explore as of how to Log Incoming Requests In Spring. We will explore different options to accomplish it along with the build in feature provided by Spring.

 

1. Log Incoming Requests In Spring

Having the ability to log incoming request in a web application is a very common requirement for modern web applications. If you are working on a REST API’s logging the incoming request can be really helpful during the development phase as it will give you clear picture about the payload and any potential issue.In this article, we will be covering how to do it using Spring’s logging filter.

2. Dependency Management

In order to add required logging dependencies, we can add spring-core, for this article, we will be using Spring Boot which will handle dependency management for us. Checkout Building an Application with Spring Boot to learn about Spring Boot dependency management. We will add Spring Boot dependencies to start our web application.

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 

3. Web Controller

In order to log incoming request, we need to have a Spring Controller in place, we will be using a simple controller for our post. Read  Creating a Web Application with Spring Boot to get an understanding of creating a web application using Spring Boot.

@RestController
public class LoggingDemoController {

    @GetMapping("/demo/greeting")
    public String sayHello(){
        return "Hello Stranger !!!";
    }
}

There is nothing special with this controller and it is simply returning "Hello Stranger !!! " to the client.

4. Custom Solutions

Spring provides interceptors to perform actions before and after web request. You can use HandlerInterceptor to create your custom implementation to log incoming requests in Spring.

You have to be careful while using such approach as input stream will be marked as consumed the moment it is read for the first time. In order to use this approach, we need to extend HandlerInterceptorAdapter and override the following two methods.

  • preHandle() – This is executed before the actual method call.
  • afterCompletion() – Method executed after the method call and our service is ready to send the response.

Alternatively, we can implement the HandlerInterceptor and provide implementation for the above two methods. Let’s have a look at the custom handler interceptor.

package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Instant;
import java.time.LocalDateTime;

@Component
public class CustomRequestInterceptor extends HandlerInterceptorAdapter {

 private static final Logger logger = LoggerFactory.getLogger(CustomRequestInterceptor.class);

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

  long startTime = Instant.now().toEpochMilli();
  logger.info("Request URL::" + request.getRequestURL().toString() +
   ":: Start Time=" + Instant.now());
  request.setAttribute("startTime", startTime);
  return true;
 }

 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

  long startTime = (Long) request.getAttribute("startTime");

  logger.info("Request URL::" + request.getRequestURL().toString() +
   ":: Time Taken=" + (Instant.now().toEpochMilli() - startTime));
 }
}<code>

As the last step, we need to register our custom interceptor usingaddInterceptors method.

@Configuration
public class RequestAppConfig implements WebMvcConfigurer {

 @Autowired
 private CustomRequestInterceptor customRequestInterceptor;

 @Override
 public void addInterceptors(InterceptorRegistry registry) {
  registry.addInterceptor(customRequestInterceptor)
   .addPathPatterns("/**/log-incoming-request/**/");;
 }
}

WebMvcConfigurer adds theCustomRequestInterceptor to the spring MVC lifecycle by invoking addInterceptors() method. When we run our application, we can see the following output in the console

2018-09-30 12:02:09.704  INFO 51707 --- [nio-8080-exec-2] com.example.CustomRequestInterceptor     : Request URL::http://localhost:8080/log-incoming-request:: Start Time=2018-09-30T06:32:08.861Z
2018-09-30 12:02:16.820  INFO 51707 --- [nio-8080-exec-2] com.example.CustomRequestInterceptor     : Request URL::http://localhost:8080/log-incoming-request:: Time Taken=9942

Be aware that once you read the payload as the input stream, it is marked as consumed and can’t be used again. So if you try to read the body payload again, an exception will be thrown. You may have to come up with option to store/ pass payload for servlet to process.

This is how the error might look like if you try to read the stream again

{
  "timestamp": 1608698124000,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
  "message": "Could not read document: Stream closed; nested exception is java.io.IOException: Stream closed",
  "path": "/v2/products/product/GUTR56"
}

We can also use Spring’s ContentCachingRequestWrapper andContentCachingResponseWrapper to work for caching the request data for logging purpose.

5. Spring Built-In Request Logging

The Spring framework comes with ready to use a feature which can log your request, all we are required to configure this ready to use solution. Spring comes with AbstractRequestLoggingFilter, that perform logging operations before and after a request is processed.

Before we get into implementation details, this filter requires a subclass to override the beforeRequest(HttpServletRequest, String) and afterRequest(HttpServletRequest, String) methods to perform the actual logging around the request.

Spring provides the following 2 implementations for AbstractRequestLoggingFilter

  1. CommonsRequestLoggingFilter
  2. ServletContextRequestLoggingFilter

ServletContextRequestLoggingFilter Simple request logging filter that writes the request URI (and optionally the query string) to the ServletContext log. We are going to discuss CommonsRequestLoggingFilter in this post.

5.1 CommonsRequestLoggingFilter using Spring Boot

Spring Boot is the new way to create and run your Spring-powered applications, we can enable CommonsRequestLoggingFilter by simply registering it as a bean with our application.

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

In addition to the above configuration, we need to make sure to set log level as DEBUG for CommonsRequestLoggingFilter either through application.properties or YAML

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

Once these configurations are in place, you should be able to see a similar output in the console

2017-10-25 19:52:02.708 DEBUG 70034 --- [io-10070-exec-4] o.s.w.f.CommonsRequestLoggingFilter      : Before request [uri=/demo/greeting;client=0:0:0:0:0:0:0:1]
2017-10-25 19:52:02.791 DEBUG 70034 --- [io-10070-exec-4] o.s.w.f.CommonsRequestLoggingFilter      : After request [uri=/demo/greeting;client=0:0:0:0:0:0:0:1]

And voila, your requests are visible in the console as well on the log files.

5.2 CommonsRequestLoggingFilter without Spring Boot

If you are not using Spring Boot, You can configure this by using traditional Filter. We have the following options to configure this in our traditional web application

  1. Configure this Filter either through xml configuration or Java configuration with default values.
  2. Create a custom filter by extending CommonsRequestLoggingFilter to modify default behaviour.
5.2.1 CommonsRequestLoggingFilter using XML

If you want to use CommonsRequestLoggingFilter with no changes, you can simply configure it in your application configuration file as a filer

<filter>
    <filter-name>requestLoggingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CommonsRequestLoggingFilter</filter-class>
    <init-param>
        <param-name>includeClientInfo</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>includePayload</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>includeQueryString</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
5.2.2 CommonsRequestLoggingFilter using Java Web Initializer

If you are not a fan of using XML configuration for your web application, Spring provides a way to configure it using WebApplicationInitializer. Please note that WebApplicationInitializer Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically.

public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic dispatcher =
                container.addServlet("dispatcher", new DispatcherServlet(appContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        container.addFilter("requestLoggingFilter", CommonsRequestLoggingFilter.class)
                .addMappingForServletNames(null, false, "dispatcher");
    }

}
5.2.3 Custom CommonsRequestLoggingFilter

If you want to customize the behavior of CommonsRequestLoggingFilter, you can always create your custom Filter by extending CommonsRequestLoggingFilter

public class CustomeRequestLoggingFilter extends CommonsRequestLoggingFilter {

    public CustomeRequestLoggingFilter(){
       super.setMaxPayLoadLength(2000);
       super.setIncludePayLoad(true);
       super.setIncludeQueryString(true);
       super.setIncludeHeaders(true);
   } 
} 

You can use any of the above options to configure your custom Filter. For more details, read CommonsRequestLoggingFilter

6. Log Incoming Requests In Spring Using Logbook

Logbook is an extensible Java library to enable complete request and response logging for different client- and server-side technologies.It also provide integration with the Spring Boot or Spring framework to provide easy to use request logging.Add the dependency in your application using pom.xml file.

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>logbook-spring-boot-starter</artifactId>
  <version>2.4.1</version>
 </dependency>

This will add all required dependencies for the Logbook, try with any of your controller, you will see a similar output in the console:

2020-12-25 15:56:20.501 TRACE 59520 --- [nio-8080-exec-2] org.zalando.logbook.Logbook              : {
    "origin":"remote",
    "type":"request",
    "correlation":"ce753171578db989",
    "protocol":"HTTP/1.1",
    "remote":"127.0.0.1",
    "method":"GET",
    "uri":"http://localhost:8080/greeting",
    "headers":{
       "accept":[
          "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
       ],
       "accept-encoding":[
          "gzip, deflate"
       ],
       "accept-language":[
          "en-US,en;q=0.5"
       ],
       "authorization":[
          "XXX"
       ],
       "cache-control":[
          "max-age=0"
       ],
       "connection":[
          "keep-alive"
       ],
       "cookie":[
          "ai_user=OP/h6|2020-09-26T17:39:24.675Z; dummyCookie=dummy_cookie; SESSION=YTljOGJiNWQtOGUxZS00MThiLWJjMTYtMDQzYTE2YTdiMzc1"
       ],
       "host":[
          "localhost:8080"
       ],
       "upgrade-insecure-requests":[
          "1"
       ],
       "user-agent":[
          "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0"
       ]
    }
 }
2020-12-25 15:56:20.590 TRACE 59520 --- [nio-8080-exec-2] org.zalando.logbook.Logbook              : {
    "origin":"local",
    "type":"response",
    "correlation":"ce753171578db989",
    "duration":50,
    "protocol":"HTTP/1.1",
    "status":200,
    "headers":{
       "Cache-Control":[
          "no-cache, no-store, max-age=0, must-revalidate"
       ],
       "Content-Length":[
          "0"
       ],
       "Content-Type":[
          "text/html;charset=UTF-8"
       ],
       "Date":[
          "Fri, 25 Dec 2020 23:56:20 GMT"
       ],
       "Expires":[
          "0"
       ],
       "Pragma":[
          "no-cache"
       ],
       "X-Content-Type-Options":[
          "nosniff"
       ],
       "X-Frame-Options":[
          "DENY"
       ],
       "X-XSS-Protection":[
          "1; mode=block"
       ]
    }
 }

It’s a powerful API and provides a lot of integration and extension point.If you are looking for a comprehensive solutions across the applications, this is a good choice to start with.

Summary

In this post, we explore as of how to Log Incoming Request in Spring. Spring comes with many hidden features which can always help us to avoid writing custom/duplicate code and CommonsRequestLoggingFilter is one of such hidden gem in Spring.

2 thoughts on “Log Incoming Requests In Spring”

Comments are closed.