お仕事で、リクエストボディのJSONの内容をログに出力したいんだと言われたので作ってみた。
実現方法
1. FilterでServletRequestをラップする
2. ServletRequestのラッパーでは、InputStreamのgetterでログ出力機能をもったServletInputStream実装を返す
3. ログ出力機能を持ったServletInputStream実装では、read要求の中で読み込んだデータをログに出力する。
Filterでボディの内容を全部ログに出力してから、ServletRequestラップして後続に処理を移譲するでも良いんだけど、ボディが全部到達するまでFilterで処理止まってしまうんで、read要求の中でログ出力するようにしている感じです。
WildFlyでの使う場合
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) { // ServletRequestをラップして後続のフィルタに処理を移譲 chain.doFilter(RequestWrapper(request as HttpServletRequest), response) } class RequestWrapper(val request: HttpServletRequest) : HttpServletRequest by request { override fun getInputStream(): ServletInputStream? { // ログ出力機能付きの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": "あいうえお" }
おわり。