Golangの高速なロガーzapとlumberjackでログを出力してrotateさせる
golanghttps://github.com/uber-go/zap
$ go get -u go.uber.org/zap
$ go get -u gopkg.in/natefinch/lumberjack.v2
速さの秘訣
Go言語のLogger「zap」は何故高速に構造化されたログを出力する事が出来るのか|株式会社CAリワード
reflectionとallocationの回避。
一度allocateしたBufferやEncoderは sync.Poolで使い回している。 このPoolはまさにallocateされたアイテムを再利用するためのもので、GCの負担を緩和させることができる。 Poolのアイテムは勝手に削除されることがあり、もし参照しか持っていなかったらそのままdeallocateされる。
https://github.com/uber-go/zap/blob/v1.4.0/buffer/pool.go#L34
func NewPool() Pool {
return Pool{p: &sync.Pool{
New: func() interface{} {
return &Buffer{bs: make([]byte, 0, _size)}
},
}}
}
使い方
現状ドキュメントが乏しいのでコードから探っていく必要がある。 まずはQuick Startから。
zap.NewProduction() は NewProductionConfig().Build(options…) のショートカット。 ConfigをBuildしてLoggerを取得し、InfoやErrorで書く流れ。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Hoge",
// Structured context as strongly-typed Field values.
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
{"level":"info","ts":1495870212.3378785,"caller":"zap-log/main.go:36","msg":"Hoge","attempt":3,"backoff":1}
NewProductionConfig() の内容はこんな感じ。ここからOutputPathを書き換えるとファイルに出力されるようにできる。
config := zap.Config{
Level: zap.NewAtomicLevelAt(zap.ErrorLevel),
Development: false,
Sampling: &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
config.OutputPaths = []string{"./aaaa.log"}
logger, _ = config.Build()
defer logger.Sync()
Buildの引数にも渡せる Option というのは apply(logger) でloggerを操作するインタフェース。 zap.Fieldsは フィールドを追加するもの。
logger = logger.WithOptions(zap.Fields(zap.String("hoge", "fuga")))
defer logger.Sync()
logger.Error("aaa",
zap.String("eee", "eee"),
)
Buildの実装をみると、 中では zapcore.NewCore(enc, sink, cfg.Level) とOptionを引数として取る New() でloggerを生成している。
enc, err := cfg.buildEncoder()
if err != nil {
return nil, err
}
sink, errSink, err := cfg.openSinks()
if err != nil {
return nil, err
}
log := New(
zapcore.NewCore(enc, sink, cfg.Level),
cfg.buildOptions(errSink)...,
)
このsinkは io.Writer をwrapしたWriteSyncer で、 zapcore.AddSync(w io.Writer) で変換できる。 これにlumberjackを渡してやるとrotateできる。
config := zap.NewProductionConfig()
enc := zapcore.NewJSONEncoder(config.EncoderConfig)
sink := zapcore.AddSync(
&lumberjack.Logger{
Filename: "./aaaa.log",
MaxSize: 500, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
},
)
logger := zap.New(
zapcore.NewCore(enc, sink, config.Level),
)
defer logger.Sync()
logger.Error("aaa",
zap.String("eeef", "eefe"),
)