はじめに
- go の web フレームワークライブラリとして chi がある
- http リクエスト/レスポンスをインターセプトして共通処理をする middleware が差し込めるようになっている
- よく使われるものは chi が用意してくれており、自前で書かなくても使えるようになっている
経緯
- リクエスト処理中に panic した時用に、panic ハンドリングをする middleware を差し込みたかった
- chi が用意してくれているものがあるので、それを使おうとした
- これが内部的にエラーログを出力するが、デフォルトだと改行されてズラズラ出力されるので、それを構造化された形(json)で出力したかった
- アプリケーション内では zap を使っており、middleware 内でもそのカスタムロガーを使ってログを吐きたかった
tl;dr
- ロガーをカスタムするのが面倒そうだったので、結局 chi の用意してくれたものを使うのではなく、panic ハンドラを自作してそれを差し込んで使うようにした
- 内容的には大した実装ではない
- 自分で書くなら、自分で作ったロガーを使って好きにログを吐くようにできる
- chi が提供している midleware の中でログを吐くのは panic ハンドラーと、アクセスロギングのハンドラーしかなく、後者も自作したかったため、この対応で特に問題なかった
- アクセスロギングは、提供されている interface に則ると、出力したいものが出力できなかったため自作した
middleware にロガーを設定する
結局使わなかったが、もし設定するならどうやるか
方法1 chi の RequestLogger middleware を使う
そもそも、chi の middleware にはカスタムロガーを設定するというオプションはなく、アクセスログを自動で吐くための RequestLogger
という middleware しか用意されていない。
この RequestLogger
の引数にロガーを渡すことができ、それは chi の定めたロガー LogFormatter
interface を実装している必要がある。
// LogFormatter initiates the beginning of a new LogEntry per request.
// See DefaultLogFormatter for an example implementation.
type LogFormatter interface {
NewLogEntry(r *http.Request) LogEntry
}
// LogEntry records the final log when a request completes.
// See defaultLogEntry for an example implementation.
type LogEntry interface {
Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})
Panic(v interface{}, stack []byte)
}
以下は chi が用意する example コード。
上記の例では logrus を使い、chi のロガー interface を満たした struct にそれを持たせて使う、という形になっている。
見ての通り、interface を満たすために、多くのコードを書かないといけなく、ちょっと辛い。
で、この middleware を設定すると内部的にはリクエスト context にそのロガーを突っ込んでおり、panic ハンドラーでは context からロガーを取り出して使う実装になっているので、副次的に panic ハンドラーのロガーも更新されるという形になっている(分かりづら...)。
それから、panic ハンドラーのロガーを変えたいだけなのに、アクセスログも出るようになってしまうし、context に勝手にロガーオブジェクトを突っ込まれてしまう。
もちろん、NewLogEntry
と Write
メソッドの実装を空にしておけば何も出力されないが、ちょっと気持ち悪い。
方法2 go-chi/httplog を使う
readme に書いてある通り、httplog
のロガーを作って httplog.RequestLogger(logger)
で middleware に設定する。
// Logger
logger := httplog.NewLogger("httplog-example", httplog.Options{
JSON: true,
})
// Service
r := chi.NewRouter()
r.Use(httplog.RequestLogger(logger))
- 単に json にしたいだけならお手軽な方法ではある
- オプションを指定することで json にしたりログレベルを変えたりなどが可能っぽい
- しかし、内部的に zerolog が使われるようで、自作のロガーを使うことはできない
よもやま
chi のリクエスト/レスポンスロギング middleware の何が微妙だったか
- レスポンスのログにパス名を含められない
- リクエストとレスポンスでそれぞれログを吐かないといけない
- このリクエストに対してこのレスポンスをした、というのを一つのログで吐きたかった
- ということで、結局以下のように自分で作った middleware を使っている