SpringBoot 사용하여 Request요청을 로깅하기 Logging HTTP Requests in SpringBoot
When logging request information, one approach is to add logs to every REST API controller. However, writing logging code in every API controller can be inefficient.
I’m going to explain how to use Spring Interceptor to intercept requests before they reach the controller’s Handler and perform separate processing.
What is an Interceptor?
As the word suggests, Interceptor means “to catch” or “intercept.” When a Client sends a request to a Server, the Request object first passes through the DispatcherServlet before being delivered to the Controller.
At this point, by placing an Interceptor between the DispatcherServlet and Controller, you can retrieve the Request object in advance.
Advantages of Using Interceptor?
- Increases code reusability by eliminating duplicate code through common code usage
- Reduces the risk of code omission since you don’t have to perform repetitive tasks individually
Types of Handler Interceptor Methods
- preHandle()
- A function that runs before the
requestenters theController. TheControllerproceeds normally only when the return value is true; if false, execution is terminated.
- A function that runs before the
- postHandle()
- Executes after the
requestenters theControllerbut before theViewis rendered
- Executes after the
- afterCompletion()
- Executes after the
requestenters theControllerand after theViewhas been successfully rendered
- Executes after the
- afterConcurrentHandlingStarted()
- A function used for asynchronous requests - replaces postHandle() and afterCompletion() methods
Using Handler Interceptor
As explained above, Handler Interceptor is an already existing interface. Therefore, I’m going to implement preHandle using method overriding.
I’ll set this up in 4 steps:
- Create a Handler Interceptor implementation and log all requests
- Register the implementation so SpringBoot can recognize it
- Wrap HttpServletRequest so that requests that can only be read once can be read multiple times
- Create a servlet filter that filters HttpServlet requests
Below is my project structure.
.
├── HELP.md
├── README.md
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── springboothandlerinterceptorsimple
│ │ ├── SpringBootHandlerInterceptorSimpleApplication.java
│ │ ├── common
│ │ │ ├── MyLoggingInterceptor.java
│ │ │ ├── RequestServletFilter.java
│ │ │ └── RequestServletWrapper.java
│ │ ├── config
│ │ │ └── Configuration.java
│ │ └── controller
│ │ └── MyTestController.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
└── test
└── java
└── com
└── example
└── springboothandlerinterceptorsimple
└── SpringBootHandlerInterceptorSimpleApplicationTests.java
Creating Handler Interceptor
package com.example.springboothandlerinterceptorsimple.common;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class MyLoggingInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(MyLoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (Objects.equals(request.getMethod(), "POST")) {
Map<String, Object> inputMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
logger.info("Request Info: " + inputMap);
logger.info("Request URL: " + request.getRequestURL());
return true;
} else {
logger.info("Request Info: " + request.getQueryString());
logger.info("Request URL: " + request.getRequestURL());
return true;
}
}
}
Registering Handler Interceptor
Create a Config package and set it up as follows.
package com.example.springboothandlerinterceptorsimple.config;
import com.example.springboothandlerinterceptorsimple.common.MyLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class Configuration implements WebMvcConfigurer {
@Autowired
private MyLoggingInterceptor myLoggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myLoggingInterceptor);
}
}
Creating Servlet Wrapper
In Spring, a request can only be read once.
Let’s wrap this request object so it can be read in multiple places.
package com.example.springboothandlerinterceptorsimple.common;
import java.io.IOException;
import java.io.StringReader;
import java.util.Scanner;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class RequestServletWrapper extends HttpServletRequestWrapper {
private String requestData = null;
public RequestServletWrapper(HttpServletRequest request) {
super(request);
try (Scanner s = new Scanner(request.getInputStream()).useDelimiter("\\A")) {
requestData = s.hasNext() ? s.next() : "";
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
StringReader reader = new StringReader(requestData);
return new ServletInputStream() {
private ReadListener readListener = null;
@Override
public int read() throws IOException {
return reader.read();
}
@Override
public void setReadListener(ReadListener listener) {
this.readListener = listener;
try {
if (!isFinished()) {
readListener.onDataAvailable();
} else {
readListener.onAllDataRead();
}
} catch (IOException io) {
io.printStackTrace();
}
}
@Override
public boolean isReady() {
return isFinished();
}
@Override
public boolean isFinished() {
try {
return reader.read() < 0;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
};
}
}
- Create a constructor that receives HttpServletRequest object and extracts it as a string
StringReader reader = new StringReader(requestData);
- Redefine InputStream with read(), setReadListener(), isFinished(), isReady() implemented
Creating Servlet Filter
package com.example.springboothandlerinterceptorsimple.common;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class RequestServletFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest wrappedRequest = new RequestServletWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
}
- Requests are wrapped using the wrapper object and this wrapper object is passed to the filter chain
- When reading the HttpServletRequest object, it reads the wrapper object even without specific mention (MyLoggingInterceptor class)
Creating Controller
Create a controller class that can receive requests by creating API endpoints.
package com.example.springboothandlerinterceptorsimple.controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class MyTestController {
@GetMapping("/test-one")
public Map<String, Object> firstAPI(@RequestParam Map<String, Object> request) {
return request;
}
@PostMapping("/test-two")
public Map<String, Object> secondAPI(@RequestBody Map<String, Object> request) {
return request;
}
}
Testing Requests
Let’s run the server and execute some requests to verify that logging is working correctly.
I created two endpoints in the controller:
test-one: GETtest-two: POST
- GET -
localhost:8080/test-one?id=1&name=wool- Result
INFO 14244 --- [nio-8080-exec-7] c.e.s.common.MyLoggingInterceptor : Request Info: id=1&name=wool INFO 14244 --- [nio-8080-exec-7] c.e.s.common.MyLoggingInterceptor : Request URL: http://localhost:8080/test-one
- Result
- POST -
localhost:8080/test-two- body :
application/json'{ "id":1, "name":"wool", "phone":"01012341234", "mail":"hello@mail.com" } - Result
INFO 14244 --- [nio-8080-exec-9] c.e.s.common.MyLoggingInterceptor : Request Info: {id=1, name=wool, phone=01012341234, mail=hello@mail.com} INFO 14244 --- [nio-8080-exec-9] c.e.s.common.MyLoggingInterceptor : Request URL: http://localhost:8080/test-two
- body :
댓글남기기