Necesitaba loggear el body del request recibido y el body del response retornado por una serie de controllers en una aplicación hecha con kotlin y Spring, y me encontré con que no hay mucha documentación al respecto, lo que dio inicio a este artículo.
Si bien spring nos deja acceder al request fácilmente, inyectando el parámetro al controller, la mayor dificultad con la que nos vamos a topar es que el request solamente lo vamos a poder leer 1 vez. Para salvar esta dificultad vamos a usar las clases ContentCachingRequestWrapper y ContentCachingResponseWrapper, que van a envolver el request original y van a cachear el contenido.
ContentCachingRequestWrapper solamente va a tener disponible el body una vez que este se haya leído, y a partir de ese momento se podrá acceder las veces que sea necesario. Para esto nos tenemos que asegurar que al momento de acceder a su body alguien (spring) haya leído el request.
Para lograr esto nos vamos a valer de un Filter, con el que vamos a insertar nuestros wrappers en medio de la cadena de ejecución.
Ver ejemplo completo: https://github.com/emilianotebes/spring-log-request-response-demo
La clase principal es LoggingFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
class LoggingFilter : Filter { override fun doFilter( req: ServletRequest, res: ServletResponse, filterChain: FilterChain ) { // Tenemos que castear los requests val castedRequest = req as HttpServletRequest val castedResponse = res as HttpServletResponse // Envolvemos los request en nuestros wrappers val wrappedRequest = ContentCachingRequestWrapper(castedRequest) val wrappedResponse = ContentCachingResponseWrapper(castedResponse) try { // Invocamos al resto de las operaciones para asegurarnos de que tanto el request como el response se lean y escriban // Notar que mandamos los wrappers, y no los objetos originales filterChain.doFilter(wrappedRequest, wrappedResponse) } finally { // Una vez ejecutado filterChain, tenemos nuestro objetos cargados con la info val contentAsString = wrappedRequest.contentAsString val responseAsString = String(wrappedResponse.contentAsByteArray) // Obligatoriamente tenemos que hacer esto, porque la conexión está establecida con el response original // y nuestro filter chain escribió en una copia, si no ponemos esta línea el body del response será vacío wrappedResponse.copyBodyToResponse() // Podemos acceder a los valores las veces que querramos println("Request: $contentAsString") println("Response: $responseAsString") } } } |