쌓고 쌓다

[스프링 부트] 모든 요청 로그 남기기(인터셉터) 본문

프로그래밍/spring

[스프링 부트] 모든 요청 로그 남기기(인터셉터)

승민아 2023. 9. 19. 12:56

스프링 인터셉터

스프링 인터셉터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
  • 스프링 인터셉터는 스프링 MVC가 제공하는 기능이기에, 디스패처 서블릿 이후에 등장한다.
  • 인터셉터 체인으로 인터셉터 다음에 인터셉터를 축

 

스프링 인터셉터 제한

1. 로그인 사용자
 HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러

2. 비 로그인 사용자
 HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출 X)

 

스프링 인터셉터 인터페이스

public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
	}
    
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
	}

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
	}

}
  • preHandle : 컨트롤러 호출 전 호출 (핸들러 어댑터 호출 전)
    • 리턴 값이 true라면 다음으로 진행, false라면 나머지 인터셉터와 핸들러 어댑터를 호출하지 않음.
  • postHandle : 컨트롤러 호출 후에 호출 
  • afterCompletion : 뷰가 렌더링 된 이후에 호출

 

스프링 인터셉터 호출 흐름 - 인프런 : 김영한

 

스프링 인터셉터 예외 발생시

  1. 컨트롤러에서 예외 발생시 postHandle은 호출되지 않는다.
  2. afterCompletion은 예외가 발생하더라도 항상 호출된다. Exception ex 파라미터로 예외 정보를 포함한다.

afterCompletion은 예외가 발생해도 항상 호출되므로 예외와 무관한 공통처리에 유용하다.

 

스프링 인터셉터 - 요청 로그

LogInterceptor

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.util.UUID;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    private static String LOG_ID = "logId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();

        // 인터셉터는 호출 시점이 분리되어 있어 preHandler에서 만든 값을 postHandle, afterCompletion에서
        // 사용하려면 request에 담아두어야 한다.
        // LogInterceptor는 싱글톤처럼 사용되기에 멤머변수로 값을 저장하면 위험하다.
        request.setAttribute(LOG_ID, uuid);

        // 핸들러 매핑에 따라 핸들러 정보가 달라진
        // @Controller, @RequestMapping은 HandlerMethod가 넘어옴
        // /resources/static 같은 정적 리소스라면 ResourceHttpRequestHandler가 넘어옴
        if(handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
        }

        log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
        return true; // true라면 정상 호출로 다음 인터셉터나 컨트롤러가 호출된다.

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
        String uuid = (String)request.getAttribute(LOG_ID);
        log.info("RESPONSE [{}][{}]", uuid, requestURI);
        if(ex!=null) {
            log.error("afterCompletion error!", ex);
        }

    }
}
  • preHandle
    • LOG_ID : 인터셉터는 호출 시점이 완전히 분리되어 있어 preHandle에서 사용한 값을 postHandle, afterCompletion에서                             사용하려면 어딘가에 담아 사용해야한다.
      • request에 담아두어 사용.
    • 리턴 값이 true여야 정상 호출로 다음 인터셉터나 컨트롤러를 이어서 호출한다.
  • afterCompletion
    • 예외가 발생하여 postHandle이 호출되지 않을 수 있으므로 afterCompletion에 종료 로그를 찍는다.

 

인터셉터 등록 - WebConfig

import com.example.board.filter.LogFilter;
import com.example.board.interceptor.LogInterceptor;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error", "/js/**");
    }
}
  • WebMvcConfigurer가 제공하는 addInterceptors를 통해 인터셉터를 등록할 수 있다.
  • order : 인터셉터의 호출 순서 (숫자가 낮을수록 먼저 호출)
  • addPathPatterns : 인터셉터를 적용할 URL 패턴 지정
  • excludePathPatterns : 인터셉터에서 제외할 패턴

 

실행 로그 결과

 

Comments