如何在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 } }