✅ 고급 활용: 요청/응답 로깅 필터

Spring 환경에서 HttpServletRequestHttpServletResponse래핑(wrap) 하여 본문 읽기 및 로깅이 가능하다.

  • 요청 래핑: CachedBodyHttpServletRequest
  • 응답 래핑: CustomResponseWrapper
  • multipart 여부 체크: CustomMultipartResolver
  • JSON 파싱 및 로깅: JsonParser, BaseResponse 활용
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class RequestResponseLoggingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);

    @Autowired
    CustomMultipartResolver customMultipartResolver;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        HttpServletRequest wrapperRequest = loggingRequest(httpServletRequest);

        CustomResponseWrapper customResponseWrapper = new CustomResponseWrapper(httpServletResponse);

        chain.doFilter(wrapperRequest, customResponseWrapper);

        loggingResponse(customResponseWrapper);
    }

    private void loggingResponse(CustomResponseWrapper customResponseWrapper) {
        StringBuilder responseLogBuilder = new StringBuilder();

        responseLogBuilder.append("\n===**[API Response]**===\n");
        responseLogBuilder.append("[StatusCode] = " + customResponseWrapper.getStatus() + "\n");
        String responseContentType = customResponseWrapper.getContentType();
        if (StringUtils.hasText(responseContentType)
                && responseContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)) {
            String responseBody = customResponseWrapper.getCaptureAsString();

            try {
                if (StringUtils.hasText(responseBody)) {
                    BaseResponse baseResponse = JsonParser.toObject(responseBody, BaseResponse.class);
                    responseLogBuilder.append("[isSuccess] = " + baseResponse.getIsSuccess() + "\n");
                    responseLogBuilder.append("[errorCode] = " + baseResponse.getErrorCode() + "\n");
                    responseLogBuilder.append("[errorMessage] = " + baseResponse.getErrorMessage() + "\n");
                } else {
                    responseLogBuilder.append("[isSuccess] = true\n");
                }
            } catch (Exception ex) {
            }
        }

        logger.info(responseLogBuilder.toString());
    }
}


✅ 1. CachedBodyServletInputStream.java

package com.testsystem.filter;

import java.io.ByteArrayInputStream;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;

public class CachedBodyServletInputStream extends ServletInputStream {

    private final ByteArrayInputStream byteArrayInputStream;

    public CachedBodyServletInputStream(ByteArrayInputStream byteArrayInputStream) {
        super();
        this.byteArrayInputStream = byteArrayInputStream;
    }

    @Override
    public boolean isFinished() {
        return this.byteArrayInputStream.available() == 0;
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public void setReadListener(ReadListener readListener) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int read() throws IOException {
        return byteArrayInputStream.read();
    }
}

✅ 2. CustomResponseWrapper.java

package com.testsystem.filter;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class CustomResponseWrapper extends HttpServletResponseWrapper {

    private final CharArrayWriter charArrayWriter = new CharArrayWriter();
    private final PrintWriter printWriter = new PrintWriter(charArrayWriter);

    public CustomResponseWrapper(HttpServletResponse response) {
        super(response);
    }

    @Override
    public PrintWriter getWriter() {
        return printWriter;
    }

    public String getCaptureAsString() {
        printWriter.flush();
        return charArrayWriter.toString();
    }
}

✅ 3. CachedBodyHttpServletRequest.java

package com.testsystem.filter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    public final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(new ByteArrayInputStream(cachedBody));
    }

    public String getCachedBody() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }
}

✅ 4. CachedBodyMultipartHttpServletRequest.java

public class CachedBodyMultipartHttpServletRequest extends HttpServletRequestWrapper
        implements MultipartHttpServletRequest {

    private final MultipartHttpServletRequest multipartHttpServletRequest;

    public CachedBodyMultipartHttpServletRequest(MultipartHttpServletRequest request) {
        super(request);
        this.multipartHttpServletRequest = request;
    }

    @Override
    public Iterator<String> getFileNames() {
        return multipartHttpServletRequest.getFileNames();
    }

    @Override
    public MultipartFile getFile(String name) {
        return multipartHttpServletRequest.getFile(name);
    }

    @Override
    public List<MultipartFile> getFiles(String name) {
        return multipartHttpServletRequest.getFiles(name);
    }

    @Override
    public Map<String, MultipartFile> getFileMap() {
        return multipartHttpServletRequest.getFileMap();
    }

    @Override
    public MultiValueMap<String, MultipartFile> getMultiFileMap() {
        return multipartHttpServletRequest.getMultiFileMap();
    }

    @Override
    public String getMultipartContentType(String paramOrFileName) {
        return multipartHttpServletRequest.getMultipartContentType(paramOrFileName);
    }

    @Override
    public HttpMethod getRequestMethod() {
        return multipartHttpServletRequest.getRequestMethod();
    }

    @Override
    public HttpHeaders getRequestHeaders() {
        return multipartHttpServletRequest.getRequestHeaders();
    }

    @Override
    public HttpHeaders getMultipartHeaders(String paramOrFileName) {
        return multipartHttpServletRequest.getMultipartHeaders(paramOrFileName);
    }
}

🔄 연결 구성도

[Client]
  ↓
RequestResponseLoggingFilter
  ↓
(CachedBodyHttpServletRequest or CachedBodyMultipartHttpServletRequest)
  ↓
Controller 처리
  ↓
(CustomResponseWrapper 로 응답 내용 로깅)

연결문서

댓글남기기