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
CommonsRequestLoggingFilter
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
- Configure this
Filter
either through xml configuration or Java configuration with default values. - 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.
You can use my library dedicated for Spring Boot request/response logging: https://github.com/piomin/spring-boot-logging.git. It also automatically integrates your application with Logstash.
I will surely try this Piotr!!