Spring WebFlux 에서 coRouter filter를 이용하여 request, response 로깅하기

Riiid Teamblog
Riiid Teamblog KR
Published in
6 min readJun 25, 2021

--

By 이신동

이신동(Shindong Lee)님은 Riiid의 Backend Engineer로 뤼이드 튜터의 도메인서버 개발을 맡고 있습니다.

1. 소개

안녕하세요. 저는 Riiid에서 토익 서버를 개발하고 있는 이신동입니다. Riiid Backend Chapter에서는 모든 서버들이 gRPC로 개발되고 있지만, B2B API 서버는 외부 파트너사를 위해 Spring WebFlux를 사용하여 REST API로 개발되었습니다. 이 글에서는 Spring WebFlux에서 coRouter filter를 이용하여 request와 response를 로깅하는 방법을 소개하려고 합니다. 그리고 그 과정에서 저희가 겪었던 문제와 해결 방법까지 말씀 드리겠습니다.

2. Spring Webflux 로깅의 문제점

Spring WebFlux에서 request body 로깅은 큰 골칫거리입니다. Request body의 타입이 Flux<DataBuffer> 라서, 로깅을 위해 한번 읽어버리면 요청을 처리하는 handler에서 body를 읽을 수 없기 때문입니다. B2B API에서는 이러한 문제 때문에 여러 handler들에서 각각 로깅을 하고 있었습니다. 예를 들면 다음과 같습니다.

하지만 이러한 handler들이 점점 많아지면서, 중복되는 로깅 코드들이 늘어났습니다. 이러한 코드들을 줄이고자 저희는 여러가지 방법을 고민했습니다.

3. coRouter filter 를 사용하게된 이유

ServerHttpRequestDecoratorgetBody 를 override 하는 방법은 handler가 요청을 처리하기 위해 body를 읽을 때 로깅 되게끔 할 수 있었습니다. 하지만 GET , DELETE 처럼 body가 없는 HTTP method 의 경우 호출되지 않기 때문에, body가 있냐 없냐에 따라 다른 방법으로 로깅해야 한다는 점이 마음에 들지 않아 선택되지 않았습니다.

WebFilter를 사용하는 방법은 coRouter에 이미 정의된 router configuration을 재사용하기 어려워 제외되었습니다.

저희는 Spring WebFlux functional endpoint에서 제공하는 coRouter의 filter를 이용하기로 했습니다.

4. coRouter filter

filterhandler들이 요청을 처리하는 곳 앞뒤로 공통의 동작을 정의할 수 있습니다. filter는 사용자가 정의하는 filterFunction 이라는 함수를 파라미터로 받습니다.

filterFunction은 두 가지 파라미터 ServerRequest 1) requestServerRequest를 받아서 ServerResponse를 리턴하는 2)handler suspend 함수를 받아서 ServerResponse를 리턴하는 suspend 함수입니다. 복잡해 보이지만 filter 에 아무것도 넣지 않을 때의 동작이 다음과 같다고 생각해보면,

handler(request) 앞뒤로 여러 가지 동작을 정의할 수 있는 간단한 함수라는 것을 알 수 있습니다.

저희는 handler 함수 앞에서는 request를 로깅하고, handler 함수 뒤에서는 response를 로깅하게끔 filterFunction 을 정의했습니다.

그리고 suspend function logRequestlogResponse를 다음과 같이 정의했습니다.

여기서 눈여겨 볼 점은 로깅을 위해 request body를 읽었기 때문에, request body가 필요한 handler를 위해 newRequest를 만들어주어야 한다는 것입니다. 이 과정을 거치지 않으면 handler에서 요청을 처리하기 위해 body를 읽다가 에러가 나게 됩니다. 그럼 이제 로깅이 되었을까요?

4. coRouter filter 의 버그

결과는 여전히 handler에서 body를 읽지 못했습니다.😭 사용자가 filterFunction의 동작을 정의하는 반면, filterFunction에 들어오는 두 개의 파라미터는 Spring에서 정의되어 있었는데요. 디버깅 결과 filterFunction 의 두 번째 파라미터 함수가 자신의 파라미터가 아닌, filterFunction 의 첫 번째 파라미터 serverRequest 를 사용하는 버그가 Spring MVC, Spring WebFlux에 모두 존재했습니다. 즉 handler는 저희가 위에서 만들어준 newRequest 가 아니라 이미 body를 읽혀버린 request 를 사용하고 있었던 것이죠 😭

Spring Framework 5.3.6 의 coRouter filter

5. coRouter filter 버그 픽스

저희는 이 버그를 고치는 PR을 올렸습니다. filterFunction 의 두 번째 파라미터 함수가 자신의 파라미터 handlerRequest 를 잘 사용하는 것이 보이시나요?

Spring Framework 5.3.7 의 coRouter filter

리뷰에서 요구한 테스트 추가 후, PR은 Spring Framework 5.3.7 에 머지되었습니다. 위에서 소개한 로깅 방법은 Spring Framework 5.3.7 (Spring Boot 2.4.6 또는 2.5.0) 이상 버전에서 정상 동작합니다.

Spring Framework 5.3.7 릴리즈에 올라간 Riiid Backend Engineers 3명

6. 결론

릴리즈까지 긴 시간이 걸렸지만 결국 문제를 해결하고 Spring 처럼 전 세계가 사용하는 서버 프레임워크에 기여했다는 점에서 매우 뜻깊은 경험이었습니다. 저희는 업데이트 된 Spring Boot 2.5.0 을 적용해서 body 를 포함한 request, response 로깅을 한 곳에서 관리할 수 있게 되었습니다.

--

--

Riiid Teamblog
Riiid Teamblog KR

교육 현장에서 실제 학습 효과를 입증하고 그 영향력을 확대하고 있는 뤼이드의 AI 기술 연구, 엔지니어링, 이를 가장 효율적으로 비즈니스화 하는 AIOps 및 개발 문화 등에 대한 실질적인 이야기를 나눕니다.