Spring BootでRequest Bodyをログに出力する
API serverをSpring Bootで作っていて、リクエストボディ(JSON文字列)をログに吐こうとしてちょっと悩んだのでメモ。
問題
Interceptor
で実装しているロギング処理でリクエストボディを取得しようとすると以下の問題にぶつかった。
HttpServletRequest#getReader
はIllegalStateException
を投げるHttpServletRequest#getInputStream#readLine
とかで読むと、Controller
でリクエストボディがないと言われるServletInputStream
はreset
に対応していない
辛い。
解決策
ググってると、HttpServletRequest
をラップして、リクエストボディを保持しておいて、そこから都度InputStream
を作ればええねん!
ってのを見つけたので、実装。例によってKotlinで書いている。
private class MyServletInputStream(bytes: ByteArray) : ServletInputStream() { private val inputStream: ByteArrayInputStream = ByteArrayInputStream(bytes) override fun read(): Int = inputStream.read() override fun read(b: ByteArray?, off: Int, len: Int): Int { return inputStream.read(b, off, len) } override fun isReady(): Boolean = inputStream.available() != 0 override fun setReadListener(listener: ReadListener?) {} override fun isFinished(): Boolean = inputStream.available() == 0 } private class MyServletRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) { private val bytes: ByteArray = ByteArrayOutputStream().use { request.inputStream.copyTo(it) it.toByteArray() } override fun getInputStream(): ServletInputStream { return MyServletInputStream(this.bytes) } }
ServletInputStream
の実装クラスのread
以外のメソッドは適当に書いた。
どう使うか
Interceptor
より前に処理が走るFilter
で、HttpServletRequst
をMyServletRequestWrapper
でラップして、FilterChain#doFilter
の引数として渡す。Interceptor
内で、HttpServletRequest#getInputStream#readLine
とかで読んでログに吐く- この
HttpServletRequest
の実態はMyServletRequestWrapper
- この
終わり