이번 글에서 활용할 Filter 코드는 아래와 같다.
( 해당 코드에 대한 설명과 과정은 아래 글을 참고 )
@Slf4j
@Component
public class LogFilter 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[] requestBody = requestWrapper.getContentAsByteArray();
log.info("request : " + new String(requestBody, StandardCharsets.UTF_8));
byte[] responseBody = responseWrapper.getContentAsByteArray();
log.info("response : " + new String(responseBody, StandardCharsets.UTF_8));
responseWrapper.copyBodyToResponse();
}
}
해당 방법은 요청/응답 값을 출력하는 것 자체는 가능하나, Controller에 요청을 우선 전달한 후 후 로그를 출력하기 때문에 스프링에 요청 값을 전달하기 전 이를 가공하거나 제한하는 등의 과정에서 제한되는 사항들이 많을 것이다.
때문에 이러한 문제를 해결하기 위해 Controller 전달 전 RequestBody값을 우선적으로 로그에 띄우려 한다.
이를 위해서는 ContentCachingRequestWrapper와 Filter 체인의 동작 방식을 알아두면 좋은데, 자세한 내용은 맨위의 링크를 통해 확인을 부탁하며, 간단하게 설명하자면
- HttpServletRequest의 InputStream은 한 번 밖에 읽지 못 한다.
- ContentCachingRequestWrapper에 값을 저장하기 위해서는 InputStream을 필수로 읽어야 한다.
- doFilter 를 활용해 Controller로 요청을 전달하는 과정에서 InputStream이 존재하여야 한다.
위와 같은 세 가지 조건을 모두 성립하기 위해서는 doFilter 를 사용하기 전, 요청 값을 복사해 저장해두고 사용하는 방법이 가장 적합하다는 생각이 들었다.
때문에 요청 값을 저장하고 가져다 쓸 수 있는 RequestWrapper 클래스를 생성했다.
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] cachedContent;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.cachedContent = StreamUtils.copyToByteArray(request.getInputStream());
}
public ServletInputStream getInputStream(){
return new ServletInputStream() {
private final InputStream cachedBodyInputStream = new ByteArrayInputStream(cachedContent);
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
};
}
}
- InputStream 값을 복사해둘 cachedContent 생성
- 클래스 선언과 동시에 HttpServletRequest값 저장
- getInputStream을 통하여 저장된 값 읽어 반환
@Slf4j
@Component
public class LogFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
RequestWrapper requestWrapper = new RequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
log.info("Request Body: " + getBody(requestWrapper.getInputStream()));
filterChain.doFilter(requestWrapper, responseWrapper);
log.info("Response Body : " + getBody(responseWrapper.getContentInputStream()));
responseWrapper.copyBodyToResponse();
}
public String getBody(InputStream is) throws IOException {
byte[] content = StreamUtils.copyToByteArray(is);
if (content.length == 0) {
return null;
}
return new String(content, StandardCharsets.UTF_8);
}
}
이와같이 코드를 작성하면 Controller에 전달 전에 body 값을 로그로 출력할 수 있다.
'Log' 카테고리의 다른 글
(AOP) 메세지 큐 요청/응답 로깅 (0) | 2025.05.11 |
---|---|
로그 구분 ID 추가 logback-spring.xml + Filter (0) | 2024.08.12 |
Filter doFilter, ContentCachingWrapper, copyBodyToResponse 사용 이유 및 예시 (0) | 2024.08.06 |
ContentCachingRequestWrapper 동작 원리 (0) | 2024.08.04 |
스프링부트 Filter를 이용한 로그(Request,Response ) 출력 (0) | 2024.08.02 |