본문 바로가기
개발/Spring

Spring Webflux Functional Endpoints 에서 request, response logging 하기

by dwony26 2021. 11. 10.
반응형

Spring Webflux에서 Functional Endpoints를 사용하면 ServerRequest, ServerResponse를 이용하여 요청과 응답을 처리하기 때문에 logging 처리가 까다롭습니다. 특히 ServerRequest의 body가 Mono에 담겨 있어 미리 읽게 되면 handler에서 사용할 수 없는 문제가 발생합니다. RouterFunction의 filter 기능을 활용하여 request body logging을 하는 방법을 알아보도록 하겠습니다.

 

우선 Java 코드입니다.

 

BlogRouter.java

@Configuration
@RequiredArgsConstructor
public class BlogRouter {

    private final BlogHandler blogHandler;
    private final RouterFilterConfig routerFilterConfig;

    @Bean
    public RouterFunction<ServerResponse> blogRoutes() {
        return RouterFunctions.route()
            .POST("/post", accept(APPLICATION_JSON), blogHandler::post)
            .filter(routerFilterConfig::routerFilter) // 1
            .build();
    }
}

1. 사용할 filter를 등록합니다. before, after를 사용할 수도 있지만 request 데이터 조작을 위해 filter로 처리하였습니다.

 

RouterFilterConfig.java

@Configuration
@Slf4j
public class RouterFilterConfig {

    public Mono<ServerResponse> routerFilter(ServerRequest request, HandlerFunction<ServerResponse> handler) {
        return request.bodyToMono(String.class) // 1
            .doOnNext(body -> requestLogging(request, body)) // 2
            .flatMap(body -> handler.handle(ServerRequest.from(request).body(body).build())) // 3
            .doOnNext(response -> responseLogging(request, response)); // 4
    }

    private void requestLogging(ServerRequest request, String body) {
        log.info("Request : {} {} | {}", request.method(), request.path(), body);
    }

    private void responseLogging(ServerRequest request, ServerResponse response) {
        log.info("Response : {} {} | {}", request.method(), request.path(), ((EntityResponse<?>) response).entity());
    }
}

1. ServerRequest의 body를 Mono로 변환합니다.

2. request body에 대한 logging입니다.

3. handler function을 실행합니다. 이 때 최초 전달된 ServerRequest의 body를 사용했으므로 새로운 ServerRequest를 만들어 handler로 전달해 줍니다.

4. response에 대한 logging입니다.

 

아래는 Kotlin 코드입니다.

 

BlogRouter.kt

@Configuration
class BlogRouter(
        private val blogHandler: BlogHandler,
        private val routerFilterConfig: RouterFilterConfig,
) {

    @Bean
    fun blogRoutes() = coRouter {
        POST("/post", accept(APPLICATION_JSON), blogHandler::post)
        filter { request, handler -> routerFilterConfig.routerFilter(request, handler) }
    }
}

 

RouterFilterConfig.kt

@Configuration
class RouterFilterConfig {
    private val logger = LoggerFactory.getLogger(this.javaClass)

    suspend fun routerFilter(request: ServerRequest, handler: suspend (ServerRequest) -> ServerResponse): ServerResponse {
        val body = request.awaitBody<String>()
        requestLogging(request, body)
        val response = handler(ServerRequest.from(request).body(body).build())
        responseLogging(request, response)
        return response
    }

    private fun requestLogging(request: ServerRequest, body: String) {
        logger.info("Request : ${request.method()} ${request.path()} | $body")
    }

    private fun responseLogging(request: ServerRequest, response: ServerResponse) {
        logger.info("Response : ${request.method()} ${request.path()} | ${(response as EntityResponse<*>).entity()}")
    }
}

 

반응형

'개발 > Spring' 카테고리의 다른 글

Spring REST Docs 적용 (feat. Asciidoctor)  (0) 2021.08.27

댓글