Project/Management

[Management] API 통신 시 로그 출력하는 필터 추가

lakelight 2022. 11. 3. 17:04
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
반응형