์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์—ˆ๋Š”๋ฐ์š”

์ด์–ด์„œ ์ด๋ฒˆ์—๋Š” ์ถœ๋ ฅ๋˜๋Š” ๋กœ๊ทธ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋ฐ”๊ฟ”๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

ํ˜น์‹œ๋ผ๋„ ๊ฒ€์ƒ‰์œผ๋กœ ์ด ํฌ์ŠคํŒ…์„ ๋จผ์ € ๋ณด๊ฒŒ๋˜์‹œ๋Š” ๋ถ„์€ ์•„๋ž˜ ํฌ์ŠคํŒ…์„ ๋จผ์ € ์ฝ์–ด๋ณด์‹œ๊ธธ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

[5๋ถ„์ฝ”๋”ฉ] Filter๋ฅผ ์ด์šฉํ•œ ์š”์ฒญ,์‘๋‹ต ๋กœ๊น… (How to log request, response info inlcuding payload using filter) - (1/2)

 

๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ๋‚ด๋ง˜๋Œ€๋กœ ๊พธ๋ฏธ๊ธฐ

์šฐ์„  ์ง€๋‚œ ์‹œ๊ฐ„์— ๋งˆ์ง€๋ง‰์œผ๋กœ ํ™•์ธํ•œ ๋กœ๊ทธ ์ถœ๋ ฅ ๋‚ด์šฉ์„ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

2022-09-16 07:32:49.190  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : Before request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1]
2022-09-16 07:32:49.226  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : After request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1, payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}]

 

๊ทธ๋Ÿผ ์ € ๋ฉ”์‹œ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์„์ง€ ํ•œ๋ฒˆ ์•Œ์•„๋ณผ๊ฒŒ์š”.

์š”์ฒญ/์‘๋‹ต ๋กœ๊น…์„ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” AbstractRequestLoggingFilter ๋ฅผ ์ƒ์†ํ•˜์—ฌ ํ•„ํ„ฐ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ”๊พธ๋ ค๋ฉด ์ € ํด๋ž˜์Šค์—์„œ ์–ด๋–ค ๋ฉ”์„œ๋“œ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋“ค์—ฌ๋‹ค๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

AbstractRequestLoggingFilter๋ฅผ ๊นŒ๋ณด๋ฉด ์•„๋ž˜ ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์„œ ์žฌ์ •์˜ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

 

doFilterInternal ๋ฉ”์„œ๋“œ์—์„œ ํ•˜๋Š” ์ผ์€ ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‹คํ–‰๋˜๊ธฐ ์ „ํ›„๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ์ž ์‹œ ์‚ดํŽด๋ณผ๊ฒŒ์š”

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {

   boolean isFirstRequest = !isAsyncDispatch(request);
   HttpServletRequest requestToUse = request;

   if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
      requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength());
   }

   boolean shouldLog = shouldLog(requestToUse);
   if (shouldLog && isFirstRequest) {
      beforeRequest(requestToUse, getBeforeMessage(requestToUse));
   }
   try {
      filterChain.doFilter(requestToUse, response);
   }
   finally {
      if (shouldLog && !isAsyncStarted(requestToUse)) {
         afterRequest(requestToUse, getAfterMessage(requestToUse));
      }
   }
}

 

beforeRequest(requestToUse, getBeforeMessage(requestToUse)) ์—์„œ Before request ... ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ๊ณ 

afterRequest(requestToUse, getAfterMessage(requestToUse)) ์—์„œ After request ... ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š”๋Œ€๋กœ ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋ฐ”๊พธ๋ ค๋ฉด ์ € ๋‘ ๊ตฐ๋ฐ๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์šฐ์„  ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ์ˆ˜์ • ์š”๊ตฌ์‚ฌํ•ญ์ด ์•„๋ž˜์™€ ๊ฐ™๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ํ˜ธ์ถœ๋ฐ›์€ API ์ •๋ณด์™€ ํด๋ผ์ด์–ธํŠธIP ์ •๋ณด๋ฅผ ์ค‘๋ณต ์ถœ๋ ฅ ํ•˜์ง€ ์•Š๋Š”๋‹ค
  2. ํ˜ธ์ถœ๋ฐ›์€ API uri ์™€ ํด๋ผ์ด์–ธํŠธIP, payload ์ •๋ณด๋Š”  before request ์—์„œ ๋‚จ๊ธด๋‹ค.  ๐ŸŒŸ
  3. after request ์—๋Š” ์‘๋‹ต์†Œ์š”์‹œ๊ฐ„๊ณผ http status ์ฝ”๋“œ๊ฐ’์„ ๋‚จ๊ธด๋‹ค.
  4. ์ตœ์ข…์ ์œผ๋กœ ์ถœ๋ ฅ๋˜๋Š” ๋ชจ์Šต์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
2022-09-16 08:28:08.053  INFO 41196 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : REQ: POST uri=/test/request-body-log;client=0:0:0:0:0:0:0:1;payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}
2022-09-16 08:28:08.076  INFO 41196 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : RES: 22ms, 400

 

์ž, ์ด์ œ ์œ„ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์šฐ๋ฆฌ์˜ ํ•„ํ„ฐํด๋ž˜์Šค๋ฅผ ์ˆ˜์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 

RequestLoggingFilter ํด๋ž˜์Šค์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    boolean isFirstRequest = !isAsyncDispatch(request);
    HttpServletRequest requestToUse = request;

    if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
        requestToUse = new ContentCachingRequestWrapper(request);
    }

    long start = System.currentTimeMillis();

    if (isFirstRequest) {
        beforeRequest(requestToUse, getBeforeMessage(requestToUse));
    }

    try {
        filterChain.doFilter(requestToUse, response);
    } finally {
        if (!isAsyncStarted(requestToUse)) {
            afterRequest(requestToUse, getAfterMessage(System.currentTimeMillis() - start, response.getStatus()));
        }
    }
}

private String getBeforeMessage(HttpServletRequest request) {
    return createMessage(request, "REQ: ", "");
}

private String getAfterMessage(long elapsed, int status) {
    return "RES: " + elapsed + "ms, " + status;
}

 

AbstractRequestLoggingFilter ์— ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” doFilterInternal ๋ฉ”์„œ๋“œ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ง€๊ณ ์™€์„œ beforeRequest์™€ afterRequest ๋ฉ”์„œ๋“œ์˜ ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜๋Š” message ๋ฅผ ์กฐ๋ฆฝํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. getBeforeMessage์˜ createMessage๋Š” superํด๋ž˜์Šค, ์ฆ‰, AbstractRequestLoggingFilter ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. createMessage ์—์„œ๋Š” prefix์™€ suffix ๊ทธ๋ฆฌ๊ณ  request ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. payload๋„ ์—ญ์‹œ ์—ฌ๊ธฐ ํฌํ•จ๋˜์–ด์žˆ์ฃ .

์ž, ๊ทธ๋Ÿผ ์•ฑ์„ ์žฌ๊ธฐ๋™ํ•˜๊ณ  ๋กœ๊น…์ด ์–ด๋–ป๊ฒŒ ๋˜๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 08:39:20.270  INFO 43287 --- [nio-8080-exec-1] c.k.demo.filter.RequestLoggingFilter     : REQ: POST /test/request-body-log, client=0:0:0:0:0:0:0:1
2022-09-16 08:39:20.306  INFO 43287 --- [nio-8080-exec-1] c.k.demo.filter.RequestLoggingFilter     : RES: 35ms, 200

 

์š”๊ตฌ์‚ฌํ•ญ ๋Œ€๋ถ€๋ถ„์ด ๋ฐ˜์˜๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋ผ? ๊ทผ๋ฐ payload ๊ฐ€ request ๋กœ๊ทธ์—์„œ ๋น ์ ธ์žˆ์Šต๋‹ˆ๋‹ค.

๋ถ„๋ช… after message ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋  ๋•Œ๋Š” ์ž˜ ์ถœ๋ ฅ์ด ๋์—ˆ๋Š”๋ฐ ๋ง์ด์ฃ . ๋˜‘๊ฐ™์€ createMessage ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ–ˆ๋Š”๋ฐ before request ์—์„œ๋Š” payload๊ฐ€ ์ถœ๋ ฅ์ด ์•ˆ๋ฉ๋‹ˆ๋‹ค ใ…œใ…œ

ํ™•์ธํ•ด๋ณด๋‹ˆ AbstractRequestLoggingFilter.getMessagePayload ์—์„œ ์•„๋ž˜ wrapper ๊ฐ€ null ๋กœ ๋ฐ˜ํ™˜์ด ๋˜๊ณ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ContentCachingRequestWrapper wrapper =
      WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);

 

res๋กœ๊ทธ์—์„œ๋Š” ์ž˜ ๋‚˜์˜ค๋˜๊ฒŒ req๋กœ๊ทธ์—์„œ ์ถœ๋ ฅํ•˜๋ ค๋‹ˆ null์ด ๋ฐ˜ํ™˜์ด ๋ฉ๋‹ˆ๋‹ค.

์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์ด ํ•„์š”ํ•œ๋ฐ ์šฐ์„  ์•„๋ž˜ ๋‘ ๊ฐœ์˜ ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

package com.keichee.demo.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class RequestBodyCacheFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        chain.doFilter(new RequestBodyCacheWrapper((HttpServletRequest) servletRequest), servletResponse);
    }
}

 

package com.keichee.demo.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

@Slf4j
public class RequestBodyCacheWrapper extends HttpServletRequestWrapper {

    private final ByteArrayInputStream byteArrayInputStream;

    public RequestBodyCacheWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream is = super.getInputStream();
        byteArrayInputStream = new ByteArrayInputStream(is.readAllBytes());
    }

    @Override
    public ServletInputStream getInputStream() {
        byteArrayInputStream.reset();
        return new MyServletInputStream(byteArrayInputStream);
    }

    @Override
    public BufferedReader getReader() {
        byteArrayInputStream.reset();
        return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8));
    }


    private static class MyServletInputStream extends ServletInputStream {

        private final InputStream is;

        public MyServletInputStream(InputStream bis) {
            is = bis;
        }

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

        @Override
        public int read(byte[] b) throws IOException {
            return is.read(b);
        }

        @Override
        public boolean isFinished() {
            return false;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {
        }
    }
}

 

์ด์ œ ๋‹ค์‹œ RequestLoggingFilter ํด๋ž˜์Šค๋กœ ๋Œ์•„์™€์„œ AbstractRequestLoggingFilter.createMessage ๋ฉ”์„œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ๊ฐ€์ง€๊ณ  ์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  getMessagePayload ๋ถ€๋ถ„์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”. ๋‹ค๋ฅธ ๋ถ€๋ถ„์€ ๊ทธ๋Œ€๋กœ ๋†”๋‘ก๋‹ˆ๋‹ค.

String payload = null;
try {
    payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
    log.error("failed to read payload", e);
}

 

 

์ˆ˜์ •์ด ์™„๋ฃŒ๋œ createMessage ๋ฉ”์„œ๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@Override
protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
    StringBuilder msg = new StringBuilder();
    msg.append(prefix);
    msg.append(request.getMethod()).append(' ');
    msg.append(request.getRequestURI());

    if (isIncludeQueryString()) {
        String queryString = request.getQueryString();
        if (queryString != null) {
            msg.append('?').append(queryString);
        }
    }

    if (isIncludeClientInfo()) {
        String client = request.getRemoteAddr();
        if (StringUtils.hasLength(client)) {
            msg.append(", client=").append(client);
        }
        HttpSession session = request.getSession(false);
        if (session != null) {
            msg.append(", session=").append(session.getId());
        }
        String user = request.getRemoteUser();
        if (user != null) {
            msg.append(", user=").append(user);
        }
    }

    if (isIncludeHeaders()) {
        HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
        if (getHeaderPredicate() != null) {
            Enumeration<String> names = request.getHeaderNames();
            while (names.hasMoreElements()) {
                String header = names.nextElement();
                if (!getHeaderPredicate().test(header)) {
                    headers.set(header, "masked");
                }
            }
        }
        msg.append(", headers=").append(headers);
    }

    if (isIncludePayload()) {
        String payload = null;
        try {
            payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("failed to read payload", e);
        }
        if (payload != null) {
            msg.append(", payload=").append(payload);
        }
    }

    msg.append(suffix);
    return msg.toString();
}

 

๋งˆ์ง€๋ง‰์œผ๋กœ doFilterInternal ๋ฉ”์„œ๋“œ ๋กœ์ง ์ค‘ filterChain.doFilter์—์„œ ์‚ฌ์šฉํ•˜๋Š” request ๊ฐ์ฒด๋ฅผ ContentCachingRequestWrapper ๊ฐ€ ์•„๋‹Œ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์€ request๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. (reuqestToUse -> request)

์ตœ์ข…์ ์œผ๋กœ RequestLoggingFilter ํด๋ž˜์Šค์˜ ์†Œ์Šค์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

package com.keichee.demo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

@Slf4j
public class RequestLoggingFilter extends AbstractRequestLoggingFilter {

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;

        if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request);
        }

        long start = System.currentTimeMillis();

        if (isFirstRequest) {
            beforeRequest(requestToUse, getBeforeMessage(requestToUse));
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            if (!isAsyncStarted(requestToUse)) {
                afterRequest(requestToUse, getAfterMessage(System.currentTimeMillis() - start, response.getStatus()));
            }
        }
    }

    private String getBeforeMessage(HttpServletRequest request) {
        return createMessage(request, "REQ: ", "");
    }

    private String getAfterMessage(long elapsed, int status) {
        return "RES: " + elapsed + "ms, " + status;
    }

    @Override
    protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
        StringBuilder msg = new StringBuilder();
        msg.append(prefix);
        msg.append(request.getMethod()).append(' ');
        msg.append(request.getRequestURI());

        if (isIncludeQueryString()) {
            String queryString = request.getQueryString();
            if (queryString != null) {
                msg.append('?').append(queryString);
            }
        }

        if (isIncludeClientInfo()) {
            String client = request.getRemoteAddr();
            if (StringUtils.hasLength(client)) {
                msg.append(", client=").append(client);
            }
            HttpSession session = request.getSession(false);
            if (session != null) {
                msg.append(", session=").append(session.getId());
            }
            String user = request.getRemoteUser();
            if (user != null) {
                msg.append(", user=").append(user);
            }
        }

        if (isIncludeHeaders()) {
            HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
            if (getHeaderPredicate() != null) {
                Enumeration<String> names = request.getHeaderNames();
                while (names.hasMoreElements()) {
                    String header = names.nextElement();
                    if (!getHeaderPredicate().test(header)) {
                        headers.set(header, "masked");
                    }
                }
            }
            msg.append(", headers=").append(headers);
        }

        if (isIncludePayload()) {
            String payload = null;
            try {
                payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                log.error("failed to read payload", e);
            }
            if (payload != null) {
                msg.append(", payload=").append(payload);
            }
        }

        msg.append(suffix);
        return msg.toString();
    }

}

 

์ž, ์ด์ œ ์•ฑ์„ ์žฌ๊ธฐ๋™ํ•˜๊ณ  ๋‹ค์‹œ API๋ฅผ ํ˜ธ์ถœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 10:04:36.918 DEBUG 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : Running with Spring Boot v2.7.3, Spring v5.3.22
2022-09-16 10:04:36.918  INFO 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : The following 1 profile is active: "local"
2022-09-16 10:04:37.301 DEBUG 57590 --- [           main] c.k.demo.filter.RequestBodyCacheFilter   : Filter 'requestBodyCacheFilter' configured for use
2022-09-16 10:04:37.301 DEBUG 57590 --- [           main] c.k.demo.filter.RequestLoggingFilter     : Filter 'loggingFilter' configured for use
2022-09-16 10:04:37.416  INFO 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : Started DemoLoggingApplication in 0.642 seconds (JVM running for 0.873)
2022-09-16 10:04:39.588  INFO 57590 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : REQ: POST /test/request-body-log, client=0:0:0:0:0:0:0:1, payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}
2022-09-16 10:04:39.627  INFO 57590 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : RES: 40ms, 200

 

request payload๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต status๋„ 200์ด ๋‚˜์˜จ๊ฒƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ž, ์ž˜ ๋”ฐ๋ผ ์˜ค์…จ๋‚˜์š”? 

์ด ๋ถ€๋ถ„์€ trickyํ•œ ๋ถ€๋ถ„์ด๋ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— request body๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ์• ๋ฅผ ๋จน๋Š” ๋ถ„๋“ค์ด ๋งŽ์Šต๋‹ˆ๋‹ค. stackoverflow์—์„œ ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๊ธฐ๋„ ํž˜๋“ค๋”๊ตฐ์š”. ์˜๋„ํ•œ๊ฑด์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์œผ๋‚˜ springboot์— ๋‚ด์žฅ๋œ tomcat์— request body๋ฅผ ํ•œ๋ฒˆ๋งŒ ์ฝ์–ด์„œ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œ(?)๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž˜๋ชป ์„ค์ •ํ•˜๋ฉด ๋กœ๊ทธ์—๋Š” payload๊ฐ€ ์ž˜ ์ถœ๋ ฅ๋˜์ง€๋งŒ ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง ์‹คํ–‰์‹œ request body๋ฅผ ๋ชป ์ฝ์–ด์™€์„œ 400 bad request๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

 

์ด์ƒ์œผ๋กœ request, response ๋กœ๊ทธ๋ฅผ ๋‚ด๋ง˜๋Œ€๋กœ ๋ฐ”๊ฟ”๋ณด๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~ ๐Ÿ™‡

์ข‹์•„์š” ๊พน ๋ˆŒ๋Ÿฌ์ฃผ๊ณ  ๊ฐ€์„ธ์š”~