go.uber.org/zap
のzap.logger
は構造化されたログを高速に出力できるとしてGolangのLoggerの中で有名だ。
https://github.com/uber-go/zap
gRPCを用いたMicroservicesを構成する際にも利用されることが多い。
https://github.com/grpc-ecosystem/go-grpc-middleware/tree/master/logging/zap
go.uber.org/zap
のzap.logger
の利用方法としてよく見るのは
zao.Config
構造体を生成し、Build()
メソッドからzap.Logger
インスタンスを生成するやり方だ。
この生成方法だと、文字列で出力先を指定するため、一見標準(エラー)出力もしくはファイルにしか出力できないように読める。
https://godoc.org/go.uber.org/zap#Config
// OutputPaths is a list of paths to write logging output to. See Open for
// details.
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
このログ出力先をbytes.Buffer
(io.Writer
)に変更する。
TL;DR
zap.Config
のBuild()
メソッドでzap.Logger
を生成するとログ出力先を標準(エラー)出力orファイルにしかできないzap.New
関数でzap.Logger
を生成すると任意のio.Writer
にログを出力できる
// 引数のbytes.Bufferにログを出力するzap.Loggerを生成する
func getDummyLogger(buf *bytes.Buffer) *zap.Logger {
encoder := zapcore.NewConsoleEncoder(...)
core := zapcore.NewCore(encoder, zapcore.AddSync(buf), zapcore.InfoLevel)
return zap.New(core)
}
zapとは
https://github.com/uber-go/zap
go.uber.org/zap
は構造化したメッセージを高速にロギングできるライブラリ。
ただ、標準ライブラリのlog
パッケージのインターフェースとzap.logger
構造体は大きく異なる。
なぜやりたかったのか
一定の処理を加えたあとにログ出力をするlogging用のMiddlewareやInterceptorを作ろうと思ったときに、
Logger
に渡すログ出力自体を検証したいと考えることがある。Go
の場合、出力先がio.Writer
インターフェースならば、テストをするときだけbytes.Buffer
などに置き換えて検証することが出来る。
しかし、zap.Config
にあるzap.Logger
を出力先を指定する設定方法は文字列指定であり、stdout
(stderr
)もしくは出力先のファイルパスしか設定出来ない。
https://godoc.org/go.uber.org/zap#Config
// OutputPaths is a list of paths to write logging output to. See Open for
// details.
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
なのでzapcore
パッケージを使う方法でio.Writer
へログ出力をするzap.Logger
インスタンスを取得する。
zap.New、zapcore.NewCoreを使ったzap.Loggerの生成
zap.Logger
のインスタンスを生成する方法はConfig
インスタンスのBuild()
メソッドを利用する他に、zap.New()
関数を利用する方法がある。
https://godoc.org/go.uber.org/zap#New
func New(core zapcore.Core, options ...Option) *Logger
New()
関数のzapcore.Core
はzapcore.NewCore
関数から生成できる。
https://godoc.org/go.uber.org/zap/zapcore#NewCore
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
この第二引数のzapcore.WriteSyncer
インターフェースがzap.New()
関数から生成したときのzap.Logger
の出力先になる。
zapcore.WriteSyncer
インターフェースはio.Writer
とSync()
メソッドを満たしていればよい。
type WriteSyncer interface {
io.Writer
Sync() error
}
そしてひとまずインターフェースを満たすだけで良いならばio.Writer
を引数にzapcore.WriteSyncer
を返す便利な関数がzapcore
パッケージに存在する。
https://godoc.org/go.uber.org/zap/zapcore#AddSync
func AddSync(w io.Writer) WriteSyncer
よって、以下のような手順を踏めば、zapcore.NewCore
関数、zap.New
関数を経由すればbytes.Buffer
(io.Writer
)にログ出力をするzap.Logger
を取得できる。
func getDummyLogger(buf *bytes.Buffer) *zap.Logger {
// ログ出力のフォーマットを指定できる
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
// TimeKey: "time", // 時刻情報は期待結果に含めにくいので省略
NameKey: "name",
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})
core := zapcore.NewCore(encoder, zapcore.AddSync(buf), zapcore.InfoLevel)
return zap.New(core)
}
終わりに
Goはシンプルな標準インターフェースとダックタイピングを提供している。
そのため、疎な設計になっていれば標準使用のみで容易にモックを使うことが出来る。