Log

스프링부트 Filter를 이용한 로그(Request,Response ) 출력

땍땍 2024. 8. 2. 02:57

스프링부트에서 로그 관리를 위해서는 크게 Filter, Interceptor,AOP 를 활용할 수 있다.

이중 Interceptor와 AOP는 스프링 내부에서 동작하는 반면, Filter는 스프링 외부에서 동작하여 스프링 내에서 가공되기 전 요청과, 가공된 후의 응답 값을 가져와 읽을 수 있다는 장점이 있어 이번에는 Filter를 활용하여 Request와 Response의 값을 로그로 출력해보려한다.

우선 로그를 출력하기 위해 아래 의존성을 추가해준다.

( lombok이 아닌 다른 의존성 혹은 방법을 사용해도 무관하다 )

implementation 'org.projectlombok:lombok'

당연하게도 로그를 띄우기 위해서는 외부에서 전달 받은 Request 값을 스프링으로 보내기 전 무조건 한 번은 읽어야 하는데, 이러한 서블릿 요청은 단 한 번만 읽을 수 있도록 설계가 되어 있어 Filter 에서 요청을 읽어 스프링으로 전달하면 오류가 발생하게 된다.

이러한 문제를 해결하기 위해서는 ContentCachingRequestWrapper 와 ContentCachingResponseWrapper 를 활용하면 되는데 이는 요청 값을 캐싱(복사) 하여 담아두어 요청을 여러번 읽을 수 있도록해 위와같은 문제를 해결할 수 있다.

@Slf4j
public class LogFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        try {
            filterChain.doFilter(requestWrapper, responseWrapper); // 요청을 필터에 전달
            log.info("request : {}", new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8)); // request body값 출력
        } finally {
            log.info("response : {} ", new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8)); // response body값 출력
            responseWrapper.copyBodyToResponse(); // 요청을 전달
        }
    }

}

이러한 Filter를 스프링 내에서 동작 시키기 위해서는 Bean 등록을 해주어야한다.

가장 간단한 방법은 해당 코드에 @Component 어노테이션을 추가해주는 방법이지만, 아래와 같이 Config 파일에 @Bean 어노테이션을 추가하여 사용해 관련 설정들을 하나의 파일에서 관리하도록 할 수도 있다.

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

}

두 방법 모두 장단점이 있기 때문에 목적에 맞게 선택하여 사용하면 될 것 같다.