Published on

Go の Web サーバで Response Body と Request Body をロギングする

Authors

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)
}

https://golang.org/pkg/net/http/#ResponseWriter

上記で作成した例は、Writeを Overwrite する形で、ログ出力用の MultiWriter に書き込むようにしています

また、middlewareで r.Body を読み取ってしまうと、次の Handler に渡せなくなってしまうため、ioutil.NopCloser を利用しています