GoでHTTPサーバのログを出力する方法
Go で HTTP サーバを構築したときにログを取得する方法を考えてみます。
ログに出力したい情報
- HTTP ステータスコード
- レスポンスボディ
HTTP ステータスコードを取得する方法は、以下の記事が詳しいです。本記事では http.ResponseWriter
を用いてレスポンスボディをロギングする方法を紹介します。
つまり、ログを出力する処理をミドルウェアパターンとして実装するというアプローチです。
エッセンスは以下の構造体によって示されます。これは http.ResponseWriter
インターフェースを埋め込むことで http.ResponseWriter
インターフェースを満たした独自の構造体を定義しています。
type loggingResponseWriter struct { http.ResponseWriter statusCode int }
http.ResponseWriter
は匿名フィールドとして与えているため、http.ResponseWriter
インターフェースが持つ以下の 3 つのメソッドを独自の loggingResponseWriter
で宣言しなかった場合は透過的に埋め込まれている http.ResponseWriter
のメソッドが用いられます。
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) }
上記のサイトでは、以下のように独自の構造体の loggingResponseWriter
に WriteHeader
を実装することで HTTP ステータスコードを取得していました。
func (lrw *loggingResponseWriter) WriteHeader(code int) { lrw.statusCode = code lrw.ResponseWriter.WriteHeader(code) }
このアプローチを拡張して、レスポンスボディもロギングすることを試みます。WriteHeader
メソッドを独自の構造体に実装するアプローチと同様に Write
メソッドを実装することで実現することができます。
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) { log.Printf("Body: %v", string(b)) return lrw.ResponseWriter.Write(b) }
もとの ResponseWriter.Write
の処理はそのまま移譲しますが、呼び出すまでにレスポンスボディをログに出力することができます。
上記の HTTP サーバにリクエストを送信すると、サーバ側に以下のようなログが出力されることが分かります。
2020/06/14 18:46:27 --> GET / 2020/06/14 18:46:27 Body: Hello, World! 2020/06/14 18:46:27 <-- 200 OK
素晴らしいですね。Write
メソッドをわずかに拡張することで透過的にレスポンスボディをロギングすることができました。
なお、レスポンスボディが大きくなることがあるため、レスポンスボディをロギングする際は nytimes/gziphandler などを用いてレスポンスボディを事前に圧縮することをおすすめします。