이전 Filter를 이용해 응답/요청 로그를 출력하는 과정에서 doFilter, ContentCachingWrapper, copyBodyToResponse 를 왜 사용하는 것인지, 사용하지 않으면 발생하는 문제를 예시와 함께 설명하기 위한 글이다.
이해를 돕기 위해 사용할 Controller 코드이다.
@RequestMapping("/api")
@RestController
@Slf4j
public class Controller {
@PostMapping("/test")
public String test(@RequestBody String a){
log.info("Controller : " + a);
return a;
}
}
우선 요청 값을 그대로 읽어 출력하는 코드를 작성해보겠다.
@Slf4j
@Component
public class LogFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("body : {}", new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
}
}
위와 같은 코드로 요청을 보내면 Filter 에서 작성한 로그만 출력이 되고, Controller 에 있는 로그는 출력이 되지 않는 것을 확인할 수가 있다. 이는 Controller에 요청이 전달이 되지 않아서인데, 이러한 요청을 전달하기 위해 사용하는 것이 doFilter 이다.
@Slf4j
@Component
public class LogFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("body : {}", new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
doFilter(request, response, filterChain);
}
}
하지만 위와 같이 doFilter를 추가해도 동일하게 body 값만이 출력이 되고 Controller에 요청이 전달되지 않는 것을 확인할 수 있다.
그 이유는 요청 값이 존재하지 않기 때문인데, 단 한 번만 읽을 수 있도록 설계된 서블릿 요청을 이미 Filter에서 body값을 출력하기 위해 InputStream을 활용해 요청 값을 읽어버렸기에 Controller에 전달할 수 있는 요청이 없어 발생하는 문제이다.
때문에 사용하는 것이 ContentCachingRequestWrapper 이다. 이를 활용해 요청 값을 여러번 읽을 수 있도록 저장해두어 Filter에서 로그를 출력하고, 동일한 요청을 Controller로 전달할 수가 있다.
그럼 ContentCachingRequestWrapper를 추가해 요청을 Controller에 전달하고 응답 값을 로그로 출력해보도록 하겠다.
@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,response);
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));
}
}
위와 같이 코드를 작성하면 요청/응답 로그는 출력이 되는 것을 확인할 수 있지만, 실제로 요청을 보낸 클라이언트(스웨거)에는 반환 값이 전달이 되지 않는 것을 볼 수 있다.
이는 요청 뿐 아닌 응답 값도 단 한 번 읽을 수 있어 발생하는 문제로, 응답 값 출력을 위해 값을 읽었기에 copyBodyToResponse를 활용해 다시 응답 값을 생성해주어야 한다.
이를 해결하기 위해서 마지막으로 copyBodyToResponse 를 추가하게 되면 정상적으로 작동하는 것을 확인할 수가 있을 것이다.
@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();
}
}
'Log' 카테고리의 다른 글
(AOP) 메세지 큐 요청/응답 로깅 (0) | 2025.05.11 |
---|---|
로그 구분 ID 추가 logback-spring.xml + Filter (0) | 2024.08.12 |
Filter 로그 처리 후 Controller에 요청 전달하기 (0) | 2024.08.08 |
ContentCachingRequestWrapper 동작 원리 (0) | 2024.08.04 |
스프링부트 Filter를 이용한 로그(Request,Response ) 출력 (0) | 2024.08.02 |