Cómo registrar los cuerpos de request y respuesta en Spring WebFlux

Quiero tener un logging centralizado para requestes y respuestas en mi API REST en Spring WebFlux con Kotlin. Hasta ahora he intentado esto enfoques

@Bean fun apiRouter() = router { (accept(MediaType.APPLICATION_JSON) and "/api").nest { "/user".nest { GET("/", userHandler::listUsers) POST("/{userId}", userHandler::updateUser) } } }.filter { request, next -> logger.info { "Processing request $request with body ${request.bodyToMono<String>()}" } next.handle(request).doOnSuccess { logger.info { "Handling with response $it" } } } 

Aquí solicitamos el método y el logging de ruta correctamente pero el cuerpo es Mono , entonces, ¿cómo debería registrarlo? ¿Debería ser al revés y tengo que suscribirme a petición del cuerpo Mono y registrarlo en la callback? Otro problema es que la interfaz ServerResponse aquí no tiene acceso al cuerpo de la respuesta. ¿Cómo puedo getlo aquí?


Otro enfoque que he probado es usar WebFilter

 @Bean fun loggingFilter(): WebFilter = WebFilter { exchange, chain -> val request = exchange.request logger.info { "Processing request method=${request.method} path=${request.path.pathWithinApplication()} params=[${request.queryParams}] body=[${request.body}]" } val result = chain.filter(exchange) logger.info { "Handling with response ${exchange.response}" } return@WebFilter result } 

El mismo problema aquí: el cuerpo de la request es Flux y no hay cuerpo de respuesta.

¿Hay alguna forma de acceder a requestes y respuestas completas para el logging desde algunos filters? ¿Qué no entiendo?

Esto es más o less similar a la situación en Spring MVC.

En Spring MVC, puede usar un filter AbstractRequestLoggingFilter y ContentCachingRequestWrapper y / o ContentCachingResponseWrapper . Muchas concesiones aquí:

  • si desea acceder a los attributes de request de servlet, debe leer y analizar el cuerpo de la request
  • Registrar el cuerpo de la request significa almacenar el cuerpo de la request, que puede usar una gran cantidad de memory
  • si desea acceder al cuerpo de la respuesta, debe ajustar la respuesta y almacenar el cuerpo de la respuesta a medida que se escribe, para su posterior recuperación

ContentCaching*Wrapper classs ContentCaching*Wrapper no existen en WebFlux pero podría crear otras similares. Pero tenga en count otros puntos aquí:

  • el almacenamiento en memory intermedia de datos de alguna manera va en contra de la stack reactiva, ya que estamos tratando de ser muy eficientes con los resources disponibles
  • no debe manipular el flujo de datos real y enjuagar más / less de lo esperado, de lo contrario correría el riesgo de interrumpir los casos de uso de la transmisión.
  • en ese nivel, solo tiene acceso a instancias de DataBuffer , que son (aproximadamente) matrices de bytes de memory eficiente. Esos pertenecen a las agrupaciones de almacenamiento intermedio y se reciclan para otros intercambios. Si no se guardan / liberan adecuadamente, se crean pérdidas de memory (y los datos de almacenamiento en búfer para un consumo posterior sin duda se ajustan a ese escenario)
  • nuevamente en ese nivel, solo son bytes y no tiene acceso a ningún códec para analizar el cuerpo HTTP. Me olvidaría de almacenar el contenido si no es legible por humanos en primer lugar

Otras respuestas a tu pregunta:

  • sí, el WebFilter es probablemente el mejor enfoque
  • no, no debe suscribirse al cuerpo de la request; de lo contrario, consumiría datos que el manejador no podrá leer; puede flatMap en la request y datos de búfer en los operadores doOn
  • envolver la respuesta debería darle acceso al cuerpo de la respuesta a medida que se escribe; no te olvides de las memory leaks, aunque

No encontré una buena manera de registrar cuerpos de request / respuesta, pero si solo está interesado en los metadatos, puede hacerlo de la siguiente manera.

 import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.server.reactive.ServerHttpResponse import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain import reactor.core.publisher.Mono @Component class LoggingFilter(val requestLogger: RequestLogger, val requestIdFactory: RequestIdFactory) : WebFilter { val logger = logger() override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { logger.info(requestLogger.getRequestMessage(exchange)) val filter = chain.filter(exchange) exchange.response.beforeCommit { logger.info(requestLogger.getResponseMessage(exchange)) Mono.empty() } return filter } } @Component class RequestLogger { fun getRequestMessage(exchange: ServerWebExchange): String { val request = exchange.request val method = request.method val path = request.uri.path val acceptableMediaTypes = request.headers.accept val contentType = request.headers.contentType return ">>> $method $path ${HttpHeaders.ACCEPT}: $acceptableMediaTypes ${HttpHeaders.CONTENT_TYPE}: $contentType" } fun getResponseMessage(exchange: ServerWebExchange): String { val request = exchange.request val response = exchange.response val method = request.method val path = request.uri.path val statusCode = getStatus(response) val contentType = response.headers.contentType return "<<< $method $path HTTP${statusCode.value()} ${statusCode.reasonPhrase} ${HttpHeaders.CONTENT_TYPE}: $contentType" } private fun getStatus(response: ServerHttpResponse): HttpStatus = try { response.statusCode } catch (ex: Exception) { HttpStatus.CONTINUE } }