如何在 Spring WebFlux 中记录请求和响应主体
我希望使用 Kotlin 在 Spring WebFlux 上的 REST API 中集中记录请求和响应.到目前为止,我已经尝试过这种方法
I want to have centralised logging for requests and responses in my REST API on Spring WebFlux with Kotlin. So far I've tried this approaches
@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
接口无法访问响应正文.我怎么能在这里得到它?
Here request method and path log successfully but the body is Mono
, so how should I log it? Should it be the other way around and I have to subscribe on request body Mono
and log it in the callback?
Another problem is that ServerResponse
interface here doesn't have access to the response body. How can I get it here?
我尝试过的另一种方法是使用 WebFilter
Another approach I've tried is using 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
而没有响应主体.
Same problem here: request body is Flux
and no response body.
有没有办法从某些过滤器访问完整的请求和响应以进行日志记录?我有什么不明白的?
Is there a way to access full request and response for logging from some filters? What don't I understand?
这或多或少类似于 Spring MVC 中的情况.
This is more or less similar to the situation in Spring MVC.
在 Spring MVC 中,您可以使用 AbstractRequestLoggingFilter
过滤器和 ContentCachingRequestWrapper
和/或 ContentCachingResponseWrapper
.这里有许多权衡:
In Spring MVC, you can use a AbstractRequestLoggingFilter
filter and ContentCachingRequestWrapper
and/or ContentCachingResponseWrapper
. Many tradeoffs here:
- 如果您想访问 servlet 请求属性,您需要实际读取和解析请求正文
- 记录请求正文意味着缓冲请求正文,这会占用大量内存
- 如果您想访问响应正文,则需要包装响应并在写入响应正文时缓冲响应正文,以便以后检索
ContentCaching*Wrapper
类在 WebFlux 中不存在,但您可以创建类似的类.但请记住这里的其他要点:
ContentCaching*Wrapper
classes don't exist in WebFlux but you could create similar ones. But keep in mind other points here:
- 在内存中缓冲数据在某种程度上违背了反应式堆栈,因为我们试图在那里非常有效地利用可用资源
- 您不应该篡改实际的数据流并且刷新频率高于/低于预期,否则您可能会破坏流式传输用例
- 在那个级别,您只能访问
DataBuffer
实例,这些实例(大致上)是内存高效的字节数组.那些属于缓冲池并被回收用于其他交换.如果这些没有正确保留/释放,则会造成内存泄漏(并且缓冲数据以供以后使用当然适合这种情况) - 再次在那个级别,它只是字节,您无权访问任何编解码器来解析 HTTP 正文.如果内容不是人类可读的,我会忘记缓冲内容
- buffering data in memory somehow goes against the reactive stack, since we're trying there to be very efficient with the available resources
- you should not tamper with the actual flow of data and flush more/less often than expected, otherwise you'd risk breaking streaming uses cases
- at that level, you only have access to
DataBuffer
instances, which are (roughly) memory-efficient byte arrays. Those belong to buffer pools and are recycled for other exchanges. If those aren't properly retained/released, memory leaks are created (and buffering data for later consumption certainly fits that scenario) - again at that level, it's only bytes and you don't have access to any codec to parse the HTTP body. I'd forget about buffering the content if it's not human-readable in the first place
您问题的其他答案:
- 是的,
WebFilter
可能是最好的方法 - 不,您不应该订阅请求正文,否则您将使用处理程序无法读取的数据;你可以在
doOn
操作符 中 - 包装响应应该可以让您访问正在写入的响应主体;但是不要忘记内存泄漏
flatMap
请求和缓冲数据- yes, the
WebFilter
is probably the best approach - no, you shouldn't subscribe to the request body otherwise you'd consume data that the handler won't be able to read; you can
flatMap
on the request and buffer data indoOn
operators - wrapping the response should give you access to the response body as it's being written; don't forget about memory leaks, though