Instead of using the presets, you can create custom loggers with the exact combinations of features that you want.
Loggers can be created using a configuration struct zap.Config
. You are expected
to fill in the struct with require values, and then call the .Build()
method
on the struct to get your logger.
cfg := zap.Config{...}
logger, err := cfg.Build()
There are no sane defaults for the struct. You have to at the minimum provide values for the three classes of settings that zap needs.
- encoder: Just adding a
Encoding: "xxx"
field is a minimum. Usingjson
here as the value will create a default JSON encoder. You can customize the encoder (which almost certainly you have to, because the defaults aren't very useful), by adding azapcore.EncoderConfig
struct to theEncoderConfig
field. - level enabler: This is a data type which allows zap to determine whether a
message at a particular level should be displayed. In the zap config struct,
you provide such a type using the
AtomicLevel
wrapper in theLevel
field. - sink: This is the destination of the log messages. You can specify multiple
output paths using the
OutputPaths
field which accepts a list of path names. Magic values like"stderr"
and"stdout"
can be used for the usual purposes.
Just mentioning an encoder type in the struct is not enough. By default the JSON encoder only outputs fields specifically provided in the log messages.
logger, _ = zap.Config{
Encoding: "json",
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
OutputPaths: []string{"stdout"},
}.Build()
logger.Info("This is an INFO message with fields", zap.String("region", "us-west"), zap.Int("id", 2))
Will output:
{"region":"us-west","id":2}
Even the message is not printed!
To add the message in the JSON encoder, you need to specify the JSON key name which will display this value in the output.
logger, _ = zap.Config{
Encoding: "json",
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
OutputPaths: []string{"stdout"},
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "message",
},
}.Build()
logger.Info("This is an INFO message with fields", zap.String("region", "us-west"), zap.Int("id", 2))
Will output:
{"message":"This is an INFO message with fields","region":"us-west","id":2}
zap can add more metadata to the message like level name, timestamp, caller, stacktrace, etc. Unless you specifically mention the JSON key in the output corresponding to these metadata, it is not added.
Some of these field names have to be paired with an encoder else zap just burns and dies (!!).
For example:
cfg := zap.Config{
Encoding: "json",
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "time",
EncodeTime: zapcore.ISO8601TimeEncoder,
CallerKey: "caller",
EncodeCaller: zapcore.ShortCallerEncoder,
},
}
logger, _ = cfg.Build()
logger.Info("This is an INFO message with fields", zap.String("region", "us-west"), zap.Int("id", 2))
Will output:
{"level":"INFO","time":"2018-05-02T16:37:54.998-0700","caller":"customlogger/main.go:91","message":"This is an INFO message with fields","region":"us-west","id":2}
Each of the encoder can be customized to fit your requirements, and some have different implementations provided by zap.
- timestamp can be output in either ISO 8601 format, or as an epoch timestamp.
- level can be capital or lowercase or even colored (even though it is probably only visible in the console output). Weirdly, the colors escape codes are not stripped in the JSON output.
- caller can be shown in short and full formats.
loggers can be cloned from an existing logger with certain modification to their behavior. This can often be useful for example, when you want to reduce code duplication by fixing a standard set of fields the logger will always output.
logger.AddCaller()
adds caller annotationlogger.AddStacktrace()
adds stacktraces for messages at and above a given levellogger.Fields()
adds specified fields to all messages output by the new loggerlogger.WrapCore()
allows you to modify or even completely replace the underlying core in the logger which combines the encoder, level and sink.
fmt.Printf("\n*** Using a JSON encoder, at debug level, sending output to stdout, all possible keys specified\n\n")
cfg := zap.Config{
Encoding: "json",
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "time",
EncodeTime: zapcore.ISO8601TimeEncoder,
CallerKey: "caller",
EncodeCaller: zapcore.ShortCallerEncoder,
},
}
logger, _ = cfg.Build()
logger.Info("This is an INFO message")
fmt.Printf("\n*** Same logger with console logging enabled instead\n\n")
logger.WithOptions(
zap.WrapCore(
func(zapcore.Core) zapcore.Core {
return zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig), zapcore.AddSync(os.Stderr), zapcore.DebugLevel)
})).Info("This is an INFO message")
Output:
*** Using a JSON encoder, at debug level, sending output to stdout, all possible keys specified
{"level":"INFO","time":"2018-05-02T16:37:54.998-0700","caller":"customlogger/main.go:90","message":"This is an INFO message"}
*** Same logger with console logging enabled instead
2018-05-02T16:37:54.998-0700 INFO customlogger/main.go:99 This is an INFO message