お仕事で、リクエストボディのJSONの内容をログに出力したいんだと言われたので作ってみた。
実現方法
1. FilterでServletRequestをラップする
2. ServletRequestのラッパーでは、InputStreamのgetterでログ出力機能をもったServletInputStream実装を返す
3. ログ出力機能を持ったServletInputStream実装では、read要求の中で読み込んだデータをログに出力する。
Filterでボディの内容を全部ログに出力してから、ServletRequestラップして後続に処理を移譲するでも良いんだけど、ボディが全部到達するまでFilterで処理止まってしまうんで、read要求の中でログ出力するようにしている感じです。
WildFly9.0.2で試したところ、デフォルトの構成だとServletRequestをラップすると怒られるので、以下コマンドでラップしたRequestやResponseを許容するようにしてあげる必要があるみたいです。
$ ./jboss-cli.sh
[disconnected /] connect
[standalone@localhost:9990 /] cd subsystem=undertow/servlet-container=default
[standalone@localhost:9990 servlet-container=default] :write-attribute(name=allow-non-standard-wrappers, value=true)
ラッパーを許容しない設定の場合に発生する例外はこれ。
Exception handling request to /loggingfilter-1.0-SNAPSHOT/app: java.lang.IllegalArgumentException: UT010023: Request siosio.LoggingFilter$RequestWrapper@77ab9fb3 was not original or a wrapper
実装例
@WebFilter(urlPatterns = arrayOf("/app/*"))
class LoggingFilter : Filter {
override fun destroy() {
}
override fun init(filterConfig: FilterConfig?) {
}
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
chain.doFilter(RequestWrapper(request as HttpServletRequest), response)
}
class RequestWrapper(val request: HttpServletRequest) : HttpServletRequest by request {
override fun getInputStream(): ServletInputStream? {
return LoggingInputStream(request.inputStream)
}
}
class LoggingInputStream(val inputStream: ServletInputStream) : ServletInputStream() {
companion object {
val logger: Logger = Logger.getLogger(LoggingInputStream::class.java)
}
override fun isReady(): Boolean {
return inputStream.isFinished
}
override fun isFinished(): Boolean {
return inputStream.isFinished
}
override fun setReadListener(readListener: ReadListener?) {
inputStream.setReadListener(readListener)
}
override fun read(b: ByteArray?): Int {
return inputStream.read(b)
}
override fun read(b: ByteArray, off: Int, len: Int): Int {
val result = inputStream.read(b, off, len)
logger.info("read http body. offset:$off, length:$result, data:${b.copyOf(result).toString("UTF-8")} ")
return result
}
override fun readLine(b: ByteArray?, off: Int, len: Int): Int {
return super.readLine(b, off, len)
}
override fun read(): Int {
return inputStream.read()
}
}
}
動かした結果
こんな感じにログに出力されますよと。
16:59:52,263 INFO [siosio.LoggingFilter$LoggingInputStream] (default task-4) read http body. offset:0, length:31, data:{ "name": "あいうえお" }
おわり。