Logging Request/Response

sixhustle·2020년 12월 10일
3

Spring

목록 보기
9/10

Logging Request/Response

  • Server개발 중, Request/Response Log는 어떻게 남기는지 알아보겠습니다.

Logback

  • Slf4j의 구현체로 Spring Boot의 기본설정이므로 별도의 라이브러리 추가가 필요 없습니다.
  • Log4j2 vs Logback

Filter vs Interceptor

  • 로그를 남기는 위치는 Filter? Interceptor? 어디가 맞는건지 모르겠습니다.
  • 아래의 사진은 Spring Lifecycle을 나타내며, DisptacherServlet 전후로 실행되고, Spring영역에 존재하는지의 차이점을 가지는 FilterInterceptor를 확인할 수 있습니다.
  • 대부분의 Blog는 Interceptor에서 Log를 처리해야한다고 나와있습니다.
  • 하지만, 현재 팀에서는 Filter에서 Log를 남기고 있고, Spring 영역에 들어오기 전 발생하는 에러를 잡기 위함이라고 했습니다.

Filter에서 Request/Response Log 남기기

  • ContentCachingRequestWrapper: 무엇인가?, HttpServletRequest는 InputStream을 한번만 읽을 수 있기 때문에(두번째부터는 IOException) Wrapping하는 것으로만 알고 있다. 이런건 어떻게 찾는것인가!!!
  • ContentCaching* class 모두 Filter 내부에서 Body에 접근하기 위해 사용된다.
  • 기존 개발자가 작업하던 부분을 Spring project에서 직접 관리하기 때문에 안정성 측면에서 훨씬 나아졌다.
  • 아래의 코드 사용시 주의점
    • Request/Response의 구현 패턴 상 WebUtils 클래스의 getNativeRequest를 사용하여 변환해야 한다.
    • ContentCachingResponseWrapper의 경우 Body를 돌려주기 위해 copyBodyToResponse method를 호출해줘야 한다.
  • 자세한 설명은 Reference sollabs.tech 참고
@Slf4j
@Component
public class RequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);

        long start = System.currentTimeMillis();
        chain.doFilter(requestWrapper, responseWrapper);
        long end = System.currentTimeMillis();

        log.info("\n" +
                        "[REQUEST] {} - {} {} - {}\n" +
                        "Headers : {}\n" +
                        "Request : {}\n" +
                        "Response : {}\n",
                ((HttpServletRequest) request).getMethod(),
                ((HttpServletRequest) request).getRequestURI(),
                responseWrapper.getStatus(),
                (end - start) / 1000.0,
                getHeaders((HttpServletRequest) request),
                getRequestBody(requestWrapper),
                getResponseBody(responseWrapper));
    }

    private Map getHeaders(HttpServletRequest request) {
        Map headerMap = new HashMap<>();

        Enumeration headerArray = request.getHeaderNames();
        while (headerArray.hasMoreElements()) {
            String headerName = (String) headerArray.nextElement();
            headerMap.put(headerName, request.getHeader(headerName));
        }
        return headerMap;
    }

    private String getRequestBody(ContentCachingRequestWrapper request) {
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                try {
                    return new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                } catch (UnsupportedEncodingException e) {
                    return " - ";
                }
            }
        }
        return " - ";
    }

    private String getResponseBody(final HttpServletResponse response) throws IOException {
        String payload = null;
        ContentCachingResponseWrapper wrapper =
                WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                wrapper.copyBodyToResponse();
            }
        }
        return null == payload ? " - " : payload;
    }
}

결과 값


References

0개의 댓글