본문 바로가기
[ JAVA ]/JAVA Spring

[ Spring ] Spring Interceptor - 요청 로그

by 환이s 2024. 1. 11.


이어서 Spring Interceptor 포스팅을 이어가겠습니다.

이전 포스팅에서 Spring Interceptor의 개념에 대해 알아봤습니다.

 

[ Spring ] Spring Interceptor - 소개

Spring Interceptor는 Servlet Filter와 같이 WEB과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술입니다. Servlet Filter가 Servlet이 제공하는 기술이라면, Spring Interceptor는 SpringMVC가 제공하는 기술

drg2524.tistory.com

 

오늘은 요청 로그를 인터셉터해서 출력해 보겠습니다.

 


요청 로그 인터셉터 생성 - LogInterceptor

 

예제로 사용될 예제 코드를 먼저 알아보겠습니다.

 

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

 	public static final 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();
         
         request.setAttribute(LOG_ID, uuid);
         
         //@RequestMapping: HandlerMethod
         //정적 리소스: ResourceHttpRequestHandler
         if (handler instanceof HandlerMethod) {
         
             HandlerMethod hm = (HandlerMethod) handler; 
             //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
             
         	}
         log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
         return true; //false 진행X
     }
     @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 logId = (String)request.getAttribute(LOG_ID);
     
     log.info("RESPONSE [{}][{}]", logId, requestURI);
     
     if (ex != null) {
     log.error("afterCompletion error!!", ex);
 		}
 	}
}

 

  • String uuid = UUID.randomUUID(). toString()
    • 요청 로그를 구분하기 위한 uuid를 생성합니다.
  • request.setAttribute(LOG_ID, uuid)
    • 서블릿 필터의 경우 지역변수로 해결이 가능하지만, 스프링 인터셉터는 호출 시점이 완전히 분리되어 있습니다.
    • 따라서 preHandle에서 지정한 값을 postHandle, afterCompletion에서 함께 사용하려면 어딘가에 담아두어야 합니다.
    • LogInterceptor도 싱글톤처럼 사용되기 때문에 멤버변수를 사용하면 위험합니다.
    • 따라서 request에 담아두었고, 이 값은 afterCompletion에서 request.getAttribute(LOG_ID)로 찾아서 사용합니다.
  • return true
    • true면 정상 호출합니다. 다음 인터셉터나 컨트롤러가 호출됩니다.
if (handler instanceof HandlerMethod) {
     HandlerMethod hm = (HandlerMethod) handler; 
     //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}

 

[ HandlerMethod ]

 

  • 핸들러 정보는 어떤 핸들러 매핑을 사용하는가에 따라 달라집니다.
  • 스프링을 사용하면 일반적으로 @Controller , @RequestMapping 애노테이션을 활용한 핸들러 매핑을 사용하는데,
  • 이 경우 핸들러 정보로 HandlerMethod가 넘어옵니다.

 

[ ResourceHttpRequestHandler ]

 

  • @Controller가 아니라 /resources/static와 같은 정적 리소스가 호출되는 경우
  • ResourceHttpRequestHandler가 핸들러 정보로 넘어오기 때문에 타입에 따라서 처리가 필요합니다.

 

[ postHandle, afterCompletion ]

 

  • 종료 로그를 postHandle이 아니라, afterCompletion에서 실행한 이유는, 예외가 발생할 경우 postHandle가 호출되지 않기 때문입니다.
  • afterCompletion은 예외가 발생해도 호출 되는 것을 보장합니다.

 

로그 출력을 위해 WebConfig 파일에 인터셉터를 등록합니다.

 


WebConfig - 인터셉터 등록

 

WebMvcConfigurer가 제공하는 addInterceptors()를 사용해서 인터셉터를 등록할 수 있습니다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

      @Override
      public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new LogInterceptor())
             .order(1)
             .addPathPatterns("/**")
             .excludePathPatterns("/css/**", "/*.ico", "/error");
     }
     //...
}

 

  • registry.addInterceptor(new LogInterceptor())
    • 인터셉터를 등록합니다.
  • order(1)
    • 인터셉터의 호출 순서를 지정합니다. (낮을수록 먼저 호출됩니다.)
  • addPathPatterns("/**")
    • 인터셉터를 적용할 URL 패턴을 지정합니다.
  • excludePathPatterns("/css/**", "/*. ico", "/error")
    • 인터셉터에서 제외할 패턴을 지정합니다.

 

이전에 포스팅한 필터와 비교해 보면 인터셉터는 addPathPatterns, excludePathPatterns로 매우 정밀하게 URL 패턴을 지정할 수 있습니다.

 


RESULT

 

 

REQUEST [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]
[hello.login.web.member.MemberController#addForm(Member)]

postHandle [ModelAndView [view="members/addMemberForm"; 
model={member=Member(id=null, loginId=null, name=null, password=null), 
org.springframework.validation.BindingResult.member=org.springframework.validat
ion.BeanPropertyBindingResult: 0 errors}]]

RESPONSE [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]

 

 

Spring URL 경로

 

Spring이 제공하는 URL  경로는 서블릿 기술이 제공하는 URL 경로와 완전히 다릅니다.

더욱 자세하고, 세밀하게 설정할 수 있는데, 자세한 내용은 아래 박스와 링크를 참고해 봅시다.

 

? 한 문자 일치
* 경로(/) 안에서 0개 이상의 문자 일치
** 경로 끝까지 0개 이상의 경로(/) 일치
{spring} 경로(/)와 일치하고 spring이라는 변수로 캡처
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
{spring:[a-z]+} regexp [a-z]+ 와 일치하고, "spring" 경로 변수로 캡처
{*spring} 경로가 끝날 때 까지 0개 이상의 경로(/)와 일치하고 spring이라는 변수로 캡처



/pages/t?st.html — matches /pages/test.html, /pages/tXst.html but not /pages/
toast.html
/resources/*.png — matches all .png files in the resources directory
/resources/** — matches all files underneath the /resources/ path, including /
resources/image.png and /resources/css/spring.css
/resources/{*path} — matches all files underneath the /resources/ path and 
captures their relative path in a variable named "path"; /resources/image.png 
will match with "path" → "/image.png", and /resources/css/spring.css will match 
with "path" → "/css/spring.css"
/resources/{filename:\\w+}.dat will match /resources/spring.dat and assign the 
value "spring" to the filename variable

 

 

PathPattern (Spring Framework 6.1.2 API)

Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern is more specific, the same or less specific than the supplied pattern.

docs.spring.io


마치며

 

오늘은 인터셉터를 활용해서 URL 요청 로그를 테스트 예제를 통해서 알아봤습니다.

 

실무 업무와 자개개발을 병행하면서 하다 보니 업로드가 늦을 수도 있는데

기초를 탄탄하게 만들기 위해 복습하는 시간을 늘렸네요. 

 

다음 포스팅은 이어서 스프링 인터셉터를 활용해서 인증 체크를 해보겠습니다.

 

위 포스팅은 김영한님의 Spring MVC 2편 - 백엔드 웹 개발 활용  강의를 참고했습니다

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 - 인프런

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

728x90