Question
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" } }
}
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?
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
}
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?
Answer
This is more or less similar to the situation in Spring MVC.
In Spring MVC, you can use a AbstractRequestLoggingFilter
filter and
ContentCachingRequestWrapper
and/or ContentCachingResponseWrapper
. Many
tradeoffs here:
- if you'd like to access servlet request attributes, you need to actually read and parse the request body
- logging the request body means buffering the request body, which can use a significant amount of memory
- if you'd like to access the response body, you need to wrap the response and buffer the response body as it's being written, for later retrieval
ContentCaching*Wrapper
classes don't exist in WebFlux but you could create
similar ones. But keep in mind other points here:
- 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
Other answers to your question:
- 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