構造化エラーをログ出力する
Go 1.13 のリリースに合わせて spiegel-im-spiegel/errs パッケージを公開したのだが,このパッケージで構成した構造化エラーをログ出力することを考える。
まぁ標準の log
パッケージでエラーメッセージを出力してもいいのだが,せっかく JSON 形式で出力できるようにしたんだから,ログ出力も JSON 形式にしたいよね。
ちうわけで,今回はこれを使います。 てってれー
まずは準備として以下の関数を考える。
import (
"os"
"github.com/spiegel-im-spiegel/errs"
)
func checkFileOpen(path string) error {
file, err := os.Open(path)
if err != nil {
return errs.Wrap(
err,
errs.WithContext("path", path),
)
}
defer file.Close()
return nil
}
ファイルをオープンするだけの関数で,オープンに失敗すると error を返す。 spiegel-im-spiegel/errs パッケージの使い方は
を参考にしてね。
この関数を使った main()
関数を書いてみよう。
まずは標準出力に対して書式 %+v
を指定して普通に
func main() {
if err := checkFileOpen("not-exist.txt"); err != nil {
fmt.Printf("%+v\n", err)
}
}
と出力する。 これの実行結果は
$ go run sample1.go | jq .
{
"Type": "*errs.Error",
"Err": {
"Type": "*os.PathError",
"Msg": "open not-exist.txt: no such file or directory",
"Cause": {
"Type": "syscall.Errno",
"Msg": "no such file or directory"
}
},
"Context": {
"function": "main.checkFileOpen",
"path": "not-exist.txt"
}
}
てな感じになる。 うむうむ。
で,ここからが本題。
fmt
.Printf()
の部分を rs/zerolog によるログ出力に置き換えてみよう。
とりあえず logger インスタンスの生成はこんな感じかな。
logger := zerolog.New(os.Stdout).Level(zerolog.DebugLevel).With().
Timestamp().
Str("role", "logger-sample").
Logger()
まずは普通に error インスタンスをログ出力してみる。
func main() {
logger := zerolog.New(os.Stdout).Level(zerolog.DebugLevel).With().
Timestamp().
Str("role", "logger-sample").
Logger()
if err := checkFileOpen("not-exist.txt"); err != nil {
logger.Error().Err(err).Send()
}
}
これの実行結果は以下の通り。
$ go run sample2.go
{"level":"error","role":"logger-sample","error":"open not-exist.txt: no such file or directory","time":"2009-11-10T23:00:00Z"}
更に jq コマンドを噛ませるとこんな感じになる。
$ go run sample.go | jq .
{
"level": "error",
"role": "logger-sample",
"error": "open not-exist.txt: no such file or directory",
"time": "2009-11-10T23:00:00Z"
}
見ての通り zerolog
.Event.Err()
メソッドでは単純なエラーメッセージしか出力されない(当たり前だが)。
通常の error ならこれで十分だが errs
.Wrap()
関数でラップした error では不十分である。
そこで zerolog
.Event.Interface()
メソッドのほうを使ってみる。
func main() {
logger := zerolog.New(os.Stdout).Level(zerolog.DebugLevel).With().
Timestamp().
Str("role", "logger-sample").
Logger()
if err := checkFileOpen("not-exist.txt"); err != nil {
logger.Error().Interface("error", err).Send()
}
}
これでログ出力は
$ go run sample3.go | jq .
{
"level": "error",
"role": "logger-sample",
"error": {
"Type": "*errs.Error",
"Err": {
"Type": "*os.PathError",
"Msg": "open not-exist.txt: no such file or directory",
"Cause": {
"Type": "syscall.Errno",
"Msg": "no such file or directory"
}
},
"Context": {
"function": "main.checkFileOpen",
"path": "not-exist.txt"
}
},
"time": "2009-11-10T23:00:00Z"
}
てな感じになった。 よーし,うむうむ,よーし。
rs/zerolog はパフォーマンスもよく使い勝手のいいパッケージで,しかも JSON 出力できるので加工しやすいだろう。 オススメである。
ブックマーク
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。