프로젝트/Ku:room

스프링 로깅 전략을 AOP에서 interceptor로 변경하기

개발하는 민우 2025. 2. 18. 21:23

왜 AOP에서 Interceptor로 변경했는가?

AOP가 메서드 수준에서 로깅을 처리하는 반면, Interceptor는 요청/응답 처리 흐름에 맞게 로깅을 적용할 수 있다.

처음에 AOP를 적용했을 때, 모든 API 호출에 대한 로깅을 DB에 저장하였다. 하지만 여기서 큰 문제점이 발생한다. 어떠한 컨트롤러의 메소드도 타지 않는 경우 (아예 이상한 url 로 요청을 보내거나 http method 를 이상하게 보내는 경우)는 로깅이 불가능하다는 점이다!

 

왜 필터가 아닌 인터셉터 방식인가?

 

  • 인터셉터(Interceptor)는 디스패처 서블릿(Dispatcher Servlet)이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있다. (스프링 컨테이너 내부에서 관리)
  • 필터(FIlter)는 디스패처 서블릿(Dispatcher Servlet)에 요청이 전달되기 전/후에 URL 패턴에 맞는 모든 요청에 부가작업을 처리할 수 있는 작업을 말한다. (필터는 스프링 범위 밖에서 처리가 되는 것이다)
  • 따라서 서버에서는 모든 요청에 대한 로깅이 아니라, API 호출에 대한 로깅을 하고 싶었기 때문에 인터셉터 방식을 사용하였다.

1. WebConfig

 

  • addInterceptors(InterceptorRegistry registry): 인터셉터를 등록할 수 있습니다. 특정 URL 패턴에 대해 인터셉터를 지정할 수 있습니다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestInterceptor())
                .order(1) // (1)
                .addPathPatterns("/**") // (2)
                .excludePathPatterns("/error"); // (3)
    }
}

 

 

2. LogUtility 클래스

public class LogUtility {

    @Getter
    @AllArgsConstructor
    public static class LogInfo {
        private String uuid;
        private String requestURI;
    }

    private static final ThreadLocal<LogInfo> REQUEST_INFO = new ThreadLocal<>(); // (1)

    public static void set(String uuid, String requestURI) {
        REQUEST_INFO.set(new LogInfo(uuid, requestURI));
    }

    public static String getUUID() {
        return REQUEST_INFO.get().getUuid();
    }

    public static String getRequestURI() {
        return REQUEST_INFO.get().getRequestURI();
    }

    public static void remove() {
        REQUEST_INFO.remove();
    }
}

 

3. RequestInterceptor

preHandle 메서드

- 요청이 컨트롤러에 도달하기 전에 실행됩니다.

 

afterCompletion 메서드

- 요청 처리 후, 뷰 렌더링이 끝난 후에 실행됩니다. 즉, 컨트롤러에서 요청을 처리하고, 뷰를 렌더링한 뒤에 호출됩니다.

 

@Slf4j
public class RequestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String uuid = UUID.randomUUID().toString(); // (1)
        String requestURI = request.getMethod() + " " + request.getRequestURL();

        log.info("Request [{}][{}]", uuid, requestURI); // (2)
        LogUtility.set(uuid, requestURI); // (3)

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        log.info("Response {} [{}][{}]", response.getStatus(), LogUtility.getUUID(), handler); // (4)
        LogUtility.remove(); // (5)
    }
}

 

webConfig 메서드

WebConfig 클래스는 WebMvcConfigurer 인터페이스를 구현하여, 스프링 MVC의 설정을 커스터마이즈합니다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestInterceptor())
                .order(1) // (1)
                .addPathPatterns("/**") // (2)
                .excludePathPatterns("/error"); // (3)
    }
}