728x90
반응형
각 코드에 log.info 또는 log.error를 출력하기 위해서
많은 코드를 추가해야합니다.
저는 Filter를 이용해서 API 호출을 할 때
그 결과를 로그로 출력하려고 합니다.
1. Filter를 상속한 LogUtil을 생성합니다.
@Component
@Order(0)
@RequiredArgsConstructor
public class LogUtil implements Filter {
}
@Component를 통해 Bean으로 등록합니다.
@Order(0)을 통해 우선적으로 필터링을 적용합니다.
2. log를 출력하기 위한 Logger를 받아오고, Json을 스트링 객체로 변환하기 위한 ObjectMapper를 받아옵니다.
private final Logger log = LoggerFactory.getLogger(this.getClass().getSimpleName());
private final ObjectMapper objectMapper;
3. Filter 내부에서 동작하는 내용을 정의합니다.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// body 값을 사용하기 위해 HttpServletRequestWrapper 를 상속한 클래스 ContentCachingRequestWrapper를 사용합니다.
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
String method = ((HttpServletRequest) request).getMethod();
String requestURI = ((HttpServletRequest) request).getRequestURI();
String queryString = ((HttpServletRequest) request).getQueryString();
String clientIP = request.getRemoteAddr();
// QueryString을 붙여서 URI를 생성합니다.
if (queryString != null)
requestURI = requestURI + "?" + queryString;
// API가 실행 완료되는 시간을 알기 위한 로직을 작성합니다.
long start = System.currentTimeMillis();
chain.doFilter(requestWrapper, responseWrapper);
long end = System.currentTimeMillis();
// API response에 대해서 info, error 분리
if(responseWrapper.getStatus()!=200){
log.error("\n" +
"[REQUEST] {} - {} {} - {}\n" +
"ClientIP : {}\n" +
"RequestBody : {}\n" +
"Response : {}\n",
method,
requestURI,
responseWrapper.getStatus(),
(end - start) / 1000.0,
clientIP,
getRequestBody(requestWrapper),
getResponseBody(responseWrapper));
} else {
log.info("\n" +
"[REQUEST] {} - {} {} - {}\n" +
"ClientIP : {}\n" +
"RequestBody : {}\n" +
"Response : {}\n",
method,
requestURI,
responseWrapper.getStatus(),
(end - start) / 1000.0,
clientIP,
getRequestBody(requestWrapper),
getResponseBody(responseWrapper));
}
}
4. doFilter에서 사용한 내부 메서드 정의
private String getRequestBody(ContentCachingRequestWrapper request){
String payload = " [] ";
// 요청 데이터의 매핑을 해제합니다.
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null){
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0){
try {
payload = new String(buf, 0, buf.length, "utf-8");
} catch (UnsupportedEncodingException e) {
}
}
}
return payload;
}
private String getResponseBody(final HttpServletResponse response) throws IOException {
String payload = " [] ";
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null){
if (wrapper.getContentType() != null && wrapper.getContentType().contains("json")){
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0){
payload = new String(buf, 0, buf.length, "utf-8");
try {
// 포맷팅 하여 스트링으로 변환합니다.
payload = this.objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this.objectMapper.readTree(payload));
} catch (Exception e) {
}
}
}
wrapper.copyBodyToResponse();
}
return payload;
}
5. 전체 코드
package art.tmes.common.util;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import lombok.RequiredArgsConstructor;
@Component
@Order(0)
@RequiredArgsConstructor
public class LogUtil implements Filter {
private final Logger log = LoggerFactory.getLogger(this.getClass().getSimpleName());
private final ObjectMapper objectMapper;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// body 값을 사용하기 위해 HttpServletRequestWrapper 를 상속한 클래스 ContentCachingRequestWrapper를 사용합니다.
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
String method = ((HttpServletRequest) request).getMethod();
String requestURI = ((HttpServletRequest) request).getRequestURI();
String queryString = ((HttpServletRequest) request).getQueryString();
String clientIP = request.getRemoteAddr();
// QueryString을 붙여서 URI를 생성합니다.
if (queryString != null)
requestURI = requestURI + "?" + queryString;
// API가 실행 완료되는 시간을 알기 위한 로직을 작성합니다.
long start = System.currentTimeMillis();
chain.doFilter(requestWrapper, responseWrapper);
long end = System.currentTimeMillis();
// API Request에 대한 로그만 남깁니다.
if(responseWrapper.getStatus()!=200){
log.error("\n" +
"[REQUEST] {} - {} {} - {}\n" +
"ClientIP : {}\n" +
"RequestBody : {}\n" +
"Response : {}\n",
method,
requestURI,
responseWrapper.getStatus(),
(end - start) / 1000.0,
clientIP,
getRequestBody(requestWrapper),
getResponseBody(responseWrapper));
} else {
log.info("\n" +
"[REQUEST] {} - {} {} - {}\n" +
"ClientIP : {}\n" +
"RequestBody : {}\n" +
"Response : {}\n",
method,
requestURI,
responseWrapper.getStatus(),
(end - start) / 1000.0,
clientIP,
getRequestBody(requestWrapper),
getResponseBody(responseWrapper));
}
}
private String getRequestBody(ContentCachingRequestWrapper request){
String payload = " [] ";
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null){
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0){
try {
payload = new String(buf, 0, buf.length, "utf-8");
} catch (UnsupportedEncodingException e) {
}
}
}
return payload;
}
private String getResponseBody(final HttpServletResponse response) throws IOException {
String payload = " [] ";
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null){
if (wrapper.getContentType() != null && wrapper.getContentType().contains("json")){
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0){
payload = new String(buf, 0, buf.length, "utf-8");
try {
payload = this.objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this.objectMapper.readTree(payload));
} catch (Exception e) {
}
}
}
wrapper.copyBodyToResponse();
}
return payload;
}
}
6. 출력 결과
INFO 22-11-03 09:35:56[http-nio-8080-exec-2] [LogUtil:61] -
[REQUEST] POST - /api/login 200 - 0.665
ClientIP : 0:0:0:0:0:0:0:1
FormData : -
MultiPart : -
RequestBody : {"userId":"id value","userPw":"password value"}
Response : {
"timestamp" : "2022-11-03T09:35:56.6045848",
"status" : 200,
"data" : {
"accessToken" : "access token value",
"refreshToken" : "refresh token value",
"userId" : "master"
}
}
이번 포스팅에서는 필터를 이용해서 한번에
로그 작업을 하는 방법을 알아보았습니다.
포스팅 봐주셔서 감사합니다.
728x90
반응형
'Project > Management' 카테고리의 다른 글
[Management] QueryDSL : orderBy() - MultiSort 동적으로 적용 (0) | 2022.11.08 |
---|---|
[Management] Nginx를 이용한 Vue.js와 Spring 배포 (0) | 2022.11.08 |
[Management] AWS 배포 빌드 파일 자동 재시작 스크립트 (0) | 2022.10.27 |
[Management] 날짜 별 로그 생성 (0) | 2022.10.24 |
[Management] DB Error : ORDER BY clause is not in SELECT list (0) | 2022.10.14 |