Go の Web サーバで Response Body と Request Body をロギングする
TL;DR
- こんな感じの middleware を実装すれば Request Body も Response Body もロギングできるようになる
// loggingWriter http.ResponseWriter の wrapper type loggingWriter struct { http.ResponseWriter multi io.Writer } // Write はmultiwriter に書き込むことで response body を記録 func (w *loggingWriter) Write(b []byte) (int, error) { return w.multi.Write(b) } // extraLog なんかいい感じの構造体に突っ込んどく // Log は極力 Json 化しておいたほうがいい (入門 監視 等の書籍に書いてある) type extraLog struct { RequestBody interface{} `json:"requestBody"` ResponseBody interface{} `json:"responseBody"` } // loggerMiddleware negroni 等を利用した middleware の実装 func loggerMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { var logBuf bytes.Buffer multiWriter := io.MultiWriter(w, &logBuf) rspWriter := &loggingWriter{w, multiWriter} // request body のロギング reqBody, err := ioutil.ReadAll(r.Body) if err != nil { log.Warnf("Failed to read request body: %s\n", err) } r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // 次のハンドラでも使えるように // loggingWriter を渡してあげる next(rspWriter, r) // あとはこれをいい感じのロギングライブラリで表示してあげるだけ extra := extraLog{ RequestBody: string(reqBody), ResponseBody: logBuf.String(), } }
背景
最近、仕事で Go を書くことが多く、最近は API Server の実装を行っていることが多いです。
その中で、サーバログとして Request Body と Response Body を出力することがあったので備忘録的にまとめておきます (セキュリティ的な観点もあるので、Request Body と Response Body をログ出力するのはあまり良しとは言えませんが...)
Request Body は割とすんなり扱えたのですが、Response Body を Middleware を挟んでログ出力するのは少し工夫が必要だったので、まとめておきます
やっていること
http.ResponseWriter
の Interface は、 以下のような定義になっています、
type ResponseWriter Interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) }
上記で作成した例は、Writeを Overwrite する形で、ログ出力用の MultiWriter
に書き込むようにしています
また、middlewareで r.Body
を読み取ってしまうと、次の Handler に渡せなくなってしまうため、ioutil.NopCloser
を利用しています