ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] request, response 로그 남기기1 - Console: 트러블슈팅
    개발기록 2022. 10. 11. 14:41
    반응형
    728x90

     

    문제 발생

    api 중 파라미터를 필요로 하는 api에서 로깅을 할 경우 파라미터가 사라지는 이슈가 발생했다.

    알고보니 httpServeltRequest를 읽는 것(getInputStream)은 일회성만 가능하다고 한다.

     

    참고: https://leeyongjin.tistory.com/entry/request-response-logging

    cf) 이전 포스트: https://jeongwoo.tistory.com/38

     

    이전 포스트에서는 파라미터가 필요없는 api에 대해 request에 파라미터를 추가해서 날리고 테스트 했기 때문에 상관이 없었다.

    하지만 실무에서 해당 이슈때문에 급하게 미봉책으로 수정한 코드를 기록으로 남긴다.

    + 다음에 시간이 되면 참고한 블로그를 따라해보든 해서 온전한 해결책을 찾아봐야겠다.

     

    해결책

    Spring-web에서 이미 구현해둔 ContentCachingRequestWrapper를 사용했다.

     

    최종 코드

    1. build.gradle

    // HttpServletRequestWrapper
    api 'org.springframework.boot:spring-boot-starter-web'

     

    2. CachingResponseWrapper.java

    cf) CachingRequestWrapper.java의 경우 ContentCachingRequestWrapper를 사용했기 때문에 쓰레기 코드가 되어 삭제했다.

     

    package com.??.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.output.TeeOutputStream;
    import org.springframework.util.FastByteArrayOutputStream;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.WriteListener;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.*;
    
    @Slf4j
    public class CachingResponseWrapper extends HttpServletResponseWrapper {
    
        private final FastByteArrayOutputStream content = new FastByteArrayOutputStream(1024);
        private ServletOutputStream outputStream;
    
        public CachingResponseWrapper(HttpServletResponse response) {
            super(response);
        }
    
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            if (this.outputStream == null) {
                this.outputStream = new CachingResponseWrapper.CachedServletOutputStream(getResponse().getOutputStream(), this.content);
            }
            return this.outputStream;
        }
    
        public InputStream getContentInputStream() {
            return this.content.getInputStream();
        }
    
        private class CachedServletOutputStream extends ServletOutputStream {
    
            private final TeeOutputStream targetStream;
    
            public CachedServletOutputStream(OutputStream one, OutputStream two) {
                targetStream = new TeeOutputStream(one, two);
            }
    
            @Override
            public void write(int arg) throws IOException {
                this.targetStream.write(arg);
            }
    
            @Override
            public void write(byte[] buf, int off, int len) throws IOException {
                this.targetStream.write(buf, off, len);
            }
    
            @Override
            public void flush() throws IOException {
                super.flush();
                this.targetStream.flush();
            }
    
            @Override
            public void close() throws IOException {
                super.close();
                this.targetStream.close();
            }
    
            @Override
            public boolean isReady() {
                return false;
            }
    
            @Override
            public void setWriteListener(WriteListener writeListener) {
                throw new UnsupportedOperationException("not support");
            }
        }
    }

     

    3. RequestResponseWrapperFilter.java

    package com.??.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    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 java.io.IOException;
    
    @Slf4j
    @Component
    public class RequestResponseWrapperFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(request);
            if (isAsyncDispatch(request)) {
                filterChain.doFilter(request, response);
            } else {
                filterChain.doFilter(wrappingRequest, new CachingResponseWrapper(response));
            }
        }
    }

     

    4. HttpLogInterceptor.java

    package com.??.interceptor;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.??.filter.CachingResponseWrapper;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.util.ContentCachingRequestWrapper;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.InputStream;
    import java.io.StringWriter;
    
    import java.net.URLDecoder;
    
    @Slf4j
    @Component
    public class HttpLogInterceptor implements HandlerInterceptor {
        private final ObjectMapper objectMapper;
    
        public HttpLogInterceptor(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            if (request instanceof ContentCachingRequestWrapper) {
                log.info("uri: " + request.getRequestURI());
            }
    
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
    
            InputStream tmpIs = ((CachingResponseWrapper) response).getContentInputStream();
            StringWriter writer = new StringWriter();
            IOUtils.copy(tmpIs, writer, "UTF-8");
            String res = writer.toString();
            log.info("response - {}", res);
        }
    }

     

    5. WebMvcConfig.java

    @Autowired로 HttpLogInterceptor httpLongInterceptor를 DI했던 것을 final로 리팩토링했다.

     

    package com.??.config;
    
    import com.??.interceptor.HttpLogInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    @ComponentScan(basePackages={"com.??.interceptor"})
    public class WebMvcConfig implements WebMvcConfigurer {
    
        private final HttpLogInterceptor httpLogInterceptor;
    
        public WebMvcConfig(HttpLogInterceptor httpLogInterceptor) {
            this.httpLogInterceptor = httpLogInterceptor;
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(httpLogInterceptor)
                    .addPathPatterns("/**");
        }
    }

     

     

    728x90
Designed by Tistory.