技術メモ

技術メモ

ラフなメモ

GoでHTTPサーバのログを出力する方法

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

上記のサイトでは、以下のように独自の構造体の loggingResponseWriterWriteHeader を実装することで 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 などを用いてレスポンスボディを事前に圧縮することをおすすめします。