ApiLogger에서 요청 바디 로그를 남길 때 getInputStream() has already been called for this request 에러 발생
원인: HttpServletRequest 객체의 InputStream은 딱 한번만 읽을 수 있음, 만약 Controller의 메소드 파라미터로 @RequestBody 어노테이션이 선언된 객체를 주입해주는 HandlerMethodArgumentResolver 구현체가 InputStream을 읽은 후 ApiLogger에서 다시 읽어버리면, 위 에러 발생
InputStream을 여러번 읽을 수 있도록 기존의 HttpServletRequest 객체를 ContentCachingRequestWrapper로 한번더 wrapping
RequestWrapperFilter가 해당 역할을 수행하며, ApplicationFilterChain의 마지막 순서로 등록됨 (WsFilter 바로 앞) -> 순서를 제일 첫번째로 등록하면 ContentCachingRequestWrapper 객체가 아닌 Servlet3SecurityContextHolderAwareRequestWrapper 객체로 감싸지는데 ApplicationFilterChain의 filters 순서 상 wrapping 순서가 꼬이는 듯?
ApplicationFilterChain에 등록된 시큐리티 관련 필터들
LoginAuthenticationFilter, JwtAuthenticationFilter는 시큐리티 관련 필터로, SecurityFilterChain에 등록됨
해당 필터들은 Filter 인터페이스를 구현하면서 @Component 어노테이션 선언으로 빈 등록되어 ApplicationFilterChain에도 포함된 것을 확인
즉 하나의 요청에 대해 해당 필터들은 2번 타게 됨
다음과 같이 설정하여 ApplicationFilterChain(서블릿 컨테이너)에는 등록되지 않고, SecurityFilterChain에만 등록되도록 설정
@Bean
public FilterRegistrationBean<LoginAuthenticationFilter> loginAuthenticationFilterRegistrationBean(AuthenticationManager authenticationManager) {
FilterRegistrationBean<LoginAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(loginAuthenticationFilter(authenticationManager));
filterRegistrationBean.setEnabled(false); //서블릿 컨테이너에 등록X
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilterRegistrationBean() {
FilterRegistrationBean<JwtAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(jwtAuthenticationFilter);
filterRegistrationBean.setEnabled(false); //서블릿 컨테이너에 등록X
return filterRegistrationBean;
}
💻 구현 내용
Spring Logback, AOP를 이용하여 로그를 남김
Spring Logback
src/main/resources/logback-spring.xml
경로의 파일을 생성하여 실행 프로필(local
,dev
,prod
) 별로 다른 로깅 방식을 취함include
태그를 통해 기본 설정값(defaults.xml
,console-appender.xml
)을 재사용springPropery
태그를 통해 logback-spring.xml 파일 내에서 사용할 변수 선언springProfile
태그를 통해 해당 설정을 적용할 실행 환경 지정local
: DEBUG 레벨의 console logger(콘솔에 로그 출력 - 휘발성)dev
: DEBUG 레벨의 console, file logger(파일에 로그 출력 - 비휘발성) + ERROR 레벨의 file loggerprod
: INFO 레벨의 console, file logger + ERROR 레벨의 file loggerSpring AOP
execution(public * com.project.foradhd..*Controller.*(..))
-> 모든-Controller
메소드 호출 시 동작하는 포인트컷 표현식로그 형식
참고
https://velog.io/@woosim34/Springboot-Logback-%EC%84%A4%EC%A0%95%ED%95%B4%EB%B3%B4%EA%B8%B0#%E2%97%8B-logback-log-level https://amaran-th.github.io/Spring/[Spring]%20Logback%EC%9C%BC%EB%A1%9C%20%EB%A1%9C%EA%B9%85(Logging)%ED%95%98%EA%B8%B0/ https://velog.io/@dktlsk6/logback-%EC%84%A4%EC%A0%95 https://backtony.tistory.com/33 https://colabear754.tistory.com/204#%EC%9D%B4%EC%A0%9C_%EB%A1%9C%EA%B7%B8%EB%A5%BC_%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94_%EB%A1%9C%EC%A7%81%EC%9D%84_%EC%9E%91%EC%84%B1%ED%95%B4%EB%B3%B4%EC%9E%90!
🛠️ 개발 오류 사항
ApiLogger
에서 요청 바디 로그를 남길 때getInputStream() has already been called for this request
에러 발생HttpServletRequest
객체의InputStream
은 딱 한번만 읽을 수 있음, 만약Controller
의 메소드 파라미터로@RequestBody
어노테이션이 선언된 객체를 주입해주는HandlerMethodArgumentResolver
구현체가InputStream
을 읽은 후ApiLogger
에서 다시 읽어버리면, 위 에러 발생InputStream
을 여러번 읽을 수 있도록 기존의HttpServletRequest
객체를ContentCachingRequestWrapper
로 한번더 wrappingRequestWrapperFilter
가 해당 역할을 수행하며,ApplicationFilterChain
의 마지막 순서로 등록됨 (WsFilter
바로 앞) -> 순서를 제일 첫번째로 등록하면ContentCachingRequestWrapper
객체가 아닌Servlet3SecurityContextHolderAwareRequestWrapper
객체로 감싸지는데ApplicationFilterChain
의filters
순서 상 wrapping 순서가 꼬이는 듯?ApplicationFilterChain
에 등록된 시큐리티 관련 필터들LoginAuthenticationFilter
,JwtAuthenticationFilter
는 시큐리티 관련 필터로,SecurityFilterChain
에 등록됨Filter
인터페이스를 구현하면서@Component
어노테이션 선언으로 빈 등록되어ApplicationFilterChain
에도 포함된 것을 확인다음과 같이 설정하여
ApplicationFilterChain
(서블릿 컨테이너)에는 등록되지 않고,SecurityFilterChain
에만 등록되도록 설정참고
https://leeyongjin.tistory.com/entry/request-response-logging https://twofootdog.github.io/Spring-POST%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%90%9C-JSON-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0/ https://bitgadak.tistory.com/10 https://www.inflearn.com/community/questions/1027744/%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%84%ED%84%B0-%EB%93%B1%EB%A1%9D-%EC%8B%9C-applicationfilterchain-%EC%97%90-%EB%93%B1%EB%A1%9D?srsltid=AfmBOoqQ-te8L8OOUKQSCbGgwx1w_Bq5augrwtzVWVH1jVSotpom2noI
🗣️ For 리뷰어
LOG_PATH
,LOG_APPLICATION_FILENAME
,LOG_ERROR_FILENAME
,LOG_MAX_HISTORY
,LOG_MAX_FILE_SIZE
,LOG_TOTAL_SIZE_CAP
추가되었습니다. 노션 확인 부탁드려요LOG_PATH
는 노션에 작성된 값이 아닌 서현님 환경에서 로그 파일을 남기고 싶은 디렉토리의 절대 경로 설정해주시면 됩니다.close #64