Logging Request/Response
- Server개발 중, Request/Response Log는 어떻게 남기는지 알아보겠습니다.
Logback
- Slf4j의 구현체로 Spring Boot의 기본설정이므로 별도의 라이브러리 추가가 필요 없습니다.
- Log4j2 vs Logback
Filter vs Interceptor
- 로그를 남기는 위치는 Filter? Interceptor?
어디가 맞는건지 모르겠습니다.
- 아래의 사진은 Spring Lifecycle을 나타내며,
DisptacherServlet
전후로 실행되고, Spring영역에 존재하는지
의 차이점을 가지는 Filter
와 Interceptor
를 확인할 수 있습니다.
- 대부분의 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
- Logging
- Logback
- Filter vs Interceptor
chain doFilter를 태우는데 이후 Error가 나면 해당 요청은 로깅이 안 남지 않나요?