如何在Spring WebFlux中记录请求和响应主体

我想用Kotlin在Spring WebFlux的REST API中集中记录请求和响应。 到目前为止我已经尝试过这种方法

@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" } } } 

这里的请求方法和路径日志成功,但身体是Mono ,所以我应该如何登录? 应该是相反的方式,我必须订阅请求正文Mono ,并在回调登录? 另一个问题是ServerResponse接口在这里不能访问响应主体。 我怎样才能在这里?


我试过的另一种方法是使用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 } 

同样的问题在这里:请求的身体是Flux和没有响应的身体。

有没有办法从一些过滤器访问完整的请求和响应? 我不明白什么?

这与Spring MVC中的情况差不多。

在Spring MVC中,可以使用AbstractRequestLoggingFilter过滤器和ContentCachingRequestWrapper和/或ContentCachingResponseWrapper 。 许多折中在这里:

  • 如果您想访问servlet请求属性,则需要实际读取和解析请求主体
  • 记录请求主体意味着缓冲请求主体,这可以使用大量的内存
  • 如果您想要访问响应主体,则需要将响应封装起来,并在写入响应主体时进行缓冲,以备日后检索

ContentCaching*Wrapper类在WebFlux中不存在,但可以创建类似的包。 但请牢记这里的其他要点:

  • 内存中的数据缓存不利于反应堆栈,因为我们正在尝试使用可用的资源非常有效
  • 你不应该篡改数据的实际流量,并且比预期更频繁地刷新,否则你将冒险打破流式使用案例
  • 在那个级别上,你只能访问DataBuffer实例,这些实例是(大致)内存高效的字节数组。 那些属于缓冲池并被回收用于其他交换。 如果没有正确保留/释放这些内存,就会创建内存泄漏(并且为了以后的使用而缓冲数据当然适合这种情况)
  • 再次在这个级别,它只是字节,你不能访问任何编解码器来解析HTTP正文。 如果这些内容不是人类可读的,我会忘记缓冲内容

你的问题的其他答案:

  • 是的, WebFilter可能是最好的方法
  • 不,你不应该订阅请求体,否则你会使用处理程序将无法读取的数据; 您可以对请求进行flatMap并在doOn运算符中缓冲数据
  • 包装响应应该让你访问正在写入的响应主体; 不过不要忘记内存泄漏

我没有找到记录请求/响应主体的好方法,但如果你只是对元数据感兴趣,那么你可以像下面这样做。

 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 } }