I am trying to set up logging using this package github.com/uber-go/zap
.
I need to write:
I tried to do this by setting up and building zap.Config
like this:
cfg = &zap.Config{
Encoding: "json",
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "time",
EncodeTime: zapcore.ISO8601TimeEncoder,
CallerKey: "caller",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: zapcore.MillisDurationEncoder,
},
}
Also I tried this way:
cfg = zap.NewProductionConfig()
cfg.OutputPaths = []string{"stdout"}
logger, err = cfg.Build(zap.AddCaller(), zap.AddCallerSkip(1))
But all logs are written to either stdout or stderr. How can I separate it?
Use zapcore.NewTee
with two cores:
zapcore.InfoLevel
to stdoutzapcore.ErrorLevel
and zapcore.FatalLevel
to stderrThis is a minimal program to demonstrate the usage:
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
func main() {
// info level enabler
infoLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level == zapcore.InfoLevel
})
// error and fatal level enabler
errorFatalLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level == zapcore.ErrorLevel || level == zapcore.FatalLevel
})
// write syncers
stdoutSyncer := zapcore.Lock(os.Stdout)
stderrSyncer := zapcore.Lock(os.Stderr)
// tee core
core := zapcore.NewTee(
zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
stdoutSyncer,
infoLevel,
),
zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
stderrSyncer,
errorFatalLevel,
),
)
// finally construct the logger with the tee core
logger := zap.New(core)
logger.Info("info log")
logger.Error("error log")
}
Note that the program above logs two messages, one at info level and one at error level. To see that it prints both to the correct device, run it and redirect stdout or stderr to /dev/null
— thus printing to the console only the other one:
$ go build main.go
$ ./main 2>/dev/null # shows only stdout
{"level":"info","ts":1626900981.520349,"msg":"info log"}
$ ./main 1>/dev/null # shows only stderr
{"level":"error","ts":1626901025.056065,"msg":"error log"}