[일정 관리 앱] 피드백 반영(2)

2024. 10. 29. 10:10내일배움캠프/Schedule Management

 해당 프로젝트에 LoggingFilter 를 구현해 두었지만 이를 제대로 사용하지 못하고 있다는 피드백을 받아 해당 필터를 수정해 보았다. 기존에는 요청 URL 과 요청이 수행되고 나서 완료 메시지만을 로그로 남겼다면 수정을 통해 요청 정보(Request Header, Parameter, Body)와 응답 정보(HttpStatus, Response Body)를 로그로 남겨보았다.

 

0. 요청/응답 정보에 대한 로그를 Filter 에서 남긴 이유

 처음에는 "그냥 적절한 곳에 로그를 남기면 되지 않을까?" 라는 생각만 있었지만 LoggingFilter 를 수정해보면서 "요청을 가장 먼저 마주하는 곳과 응답을 가장 마지막에 마주하는 곳이 Filter(프로젝트에서는 LoggingFilter)이니 여기서 로깅을 하는 것이 적절한 것 같다." 라는 생각을 가지게 되어 최종적으로 LoggingFilter 에 요청/응답에 대한 로그를 남기도록 수정하게 되었다.

 

1. 요청에 대한 Logging

 다른 부분은 괜찮았지만 요청 데이터(Request Body)를 Filter 에서 먼저 조회할 경우 Controller 에서 @RequestBody 를 통해 요청 데이터를 읽지 못하는 상황을 겪었다. 이는 HttpServletRequest 의 구조상 객체를 한 번 읽을 수 있도록 설계되어 있기 때문(정확히는 Servlet 구조상 문제)이었다. 즉, Filter 에서 객체를 읽어 버릴 경우 Controller 에서는 요청 객체를 읽을 수 없다는 것이다.

 

그래서 나의 경우 Spring 이 제공하는 ContentCachingRequestWrapper 를 활용해 요청 객체를 감싸 여러 번 읽을 수 있도록 하였다.

HttpServletRequest httpRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpRequest);

chain.doFilter(requestWrapper, responseWrapper);

log.info("request URL: {}", httpRequest.getRequestURI());
log.info("authorization: {}", httpRequest.getHeader("Authorization"));
if (StringUtils.hasText(requestWrapper.getQueryString())) {
    log.info("queryParameter: {}", URLDecoder.decode(requestWrapper.getQueryString(), StandardCharsets.UTF_8));
}
log.info("requestBody: \n{}", new String(requestWrapper.getContentAsByteArray()));

 

또한 ContentCachingRequestWrapper 의 경우 요청 데이터를 무조건 한 번 읽어야 해당 데이터가 캐싱되기에 'chain.doFilter()' 이후에 요청 데이터를 로깅하도록 작성하였다. 'chain.doFilter()' 가 수행되면 Controller 에서 @RequestBody 를 통해 요청 데이터를 읽게되고 이후에는 ContentCachingRequestWrapper 객체에 캐싱된 요청 데이터를 조회해 로그로 남기는 것이다.

 

 

2. 응답에 대한 Logging

 응답의 경우에도 요청과 유사하게 작성했다. 차이점은 ContentCachingResponseWrapper 를 사용한 것이다.

HttpServletResponse httpResponse = (HttpServletResponse) response;
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse);

chain.doFilter(requestWrapper, responseWrapper);

log.info("response status code: {}", httpResponse.getStatus());
log.info("responseBody: {}", new String(responseWrapper.getContentAsByteArray()));

responseWrapper.copyBodyToResponse();

 

요청의 경우 'chain.doFilter()' 이후에 로그를 작성해 Controller 에서 요청 데이터를 읽을 수 있었다. 응답의 경우 응답 데이터가 WAS 에 전달 되어야 하기에 코드 마지막에 'responseWrapper.copyBodyToResponse()' 를 호출해 응답 데이터를 캐싱하여 응답 객체를 다시 읽을 수 있도록 하였다.

 

 

3. 이대로 마무리?

 일단 소기의 목적은 달성했다. 피드백 내용대로 요청의 헤더, 쿼리 파라미터, 바디(데이터) 정보와 응답 상태(HttpStatus), 바디(데이터)를 로그로 남길 수 있었다. 하지만 막상 이렇게 수정해보니 "끝" 이라는 느낌은 강하게 받지 못했다.

 

Response Body 의 내용을 JSON 타입으로 매핑하지 않은 부분이나, 쿼리 파라미터를 갖지 않은 요청에 대한 부분(임시로 if 문을 사용해 처리하긴 했지만..), 민감정보에 대한 처리 등에 대한 고민을 통해 'Logging' 이라는 행위 또한 깊게 알아둘 필요성을 느꼈기 때문이다.

 

이번 기회를 통해 'Logging' 은 적절한 위치에 적절한 내용의 로그를 남기는 행위라 좀 더 이해할 수 있었지만 어떤 방식의 Logging 이 효율적이고 작성자의 의도를 명시적으로 나타낼 수 있는지에 대해서는 좀 더 알아볼 필요가 있다 생각한다.