반응형
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 |
---|
댓글