본문 바로가기

스프링/Spring MVC

서블릿 필터 사용해 요청 로그 남기기

공통 관심 사항

애플리케이션 여러 로직에서 공통으로 관심이 있는 있는 것을 공통 관심사(cross-cutting concern)라고 합니다.

만약 게시물을 관리하는 것을 로그인한 사용자에게만 허락한다면 게시물 등록, 수정, 삭제, 조회 등등 여러 로직에서 공통으로 인증에 대해서 관심을 가지고 있습니다.

웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요한데, 서블릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공합니다.

 

서블릿 필터 소개

필터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러

필터를 적용하면 필터가 호출된 다음에 서블릿이 호출됩니다.

그래서 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 됩니다.

참고로 필터는 특정 URL 패턴에 적용할 수 있습니다. /*이라고 하면 모든 요청에 필터가 적용됩니다.

 

필터 제한

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

필터에서 적절하지 않은 요청이라고 판단하면 거기에서 끝을 낼 수도 있다. 그래서 로그인 여부를 체크하기에 딱 좋습니다/

 

필터 체인

HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러

필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있다. 예를 들어서 로그를 남기는 필터를

먼저 적용하고, 그다음에 로그인 여부를 체크하는 필터를 만들 수 있습니다.

 

필터 인터페이스

public interface Filter {
	public default void init(FilterConfig filterConfig) throws ServletException{}
	public void doFilter(ServletRequest request,
    	ServletResponse response, FilterChain chain) throws IOException, ServletException;
	public default void destroy() {}
}

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리합니다.

  • init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출됩니다.
  • doFilter(): 고객의 요청이 올 때마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 됩니다.
  • destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출됩니다.

 

모든 요청 로그로 남기는 필터 만들기

LogFilter - 로그 필터

@Slf4j
public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        try {
            log.info("REQUEST [{}][{}]", uuid, requestURI);
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            log.info("RESPONSE [{}][{}]", uuid, requestURI);
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}
  • public class LogFilter implements Filter {}
    • 필터를 사용하려면 필터 인터페이스를 구현해야 합니다.
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    • HTTP 요청이 오면 doFilter 가 호출됩니다.
    • ServletRequest request 는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스이다. HTTP를 사용하면 HttpServletRequest httpRequest = (HttpServletRequest) request; 와 같이 다운 캐스팅 하면 됩니다.
  • String uuid = UUID.randomUUID().toString();
    • HTTP 요청을 구분하기 위해 요청당 임의의 uuid를 생성해둡니다.
  • log.info("REQUEST [{}][{}]", uuid, requestURI);
    • uuid와 requestURI를 출력합니다.
  • chain.doFilter(request, response);
    • 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출합니다. 만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않습니다.

 

WebConfig - 필터 설정

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}
  • 스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록하면 됩니다.
  • setFilter(new LogFilter()) : 등록할 필터를 지정합니다.
  • setOrder(1) : 필터는 체인으로 동작한다. 따라서 순서가 필요하다. 낮을수록 먼저 동작합니다.
  • addUrlPatterns("/*") : 필터를 적용할 URL 패턴을 지정한다. 한 번에 여러 패턴을 지정할 수 있습니다.
참고
URL 패턴에 대한 룰은 필터도 서블릿과 동일하다. 자세한 내용은 서블릿 URL 패턴으로 검색해보면 됩니다.