I would like to duplicate entry keys like caller
with another key name method
and show both in the log...
{"level":"error", "caller: "testing/testing.go:1193", "method": "testing/testing.go:1193", "message": "foo"}
Any ideas?
You can't change the fields of a zapcore.Entry
. You may change how it is marshalled, but honestly adding ghost fields to a struct is a bad hack. What you can do is use a custom encoder, and append to []zapcore.Field
a new string item with a copy of the caller. In particular, the default output of the JSON encoder is obtained from Caller.TrimmedPath()
:
type duplicateCallerEncoder struct {
zapcore.Encoder
}
func (e *duplicateCallerEncoder) Clone() zapcore.Encoder {
return &duplicateCallerEncoder{Encoder: e.Encoder.Clone()}
}
func (e *duplicateCallerEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// appending to the fields list
fields = append(fields, zap.String("method", entry.Caller.TrimmedPath()))
return e.Encoder.EncodeEntry(entry, fields)
}
Note that the above implements Encoder.Clone()
. See this for details: Why custom encoding is lost after calling logger.With in Uber Zap?
And then you can use it by either constructing a new Zap core, or by registering the custom encoder. The registered constructor embeds a JSONEncoder
into your custom encoder, which is the default encoder for the production logger:
func init() {
// name is whatever you like
err := zap.RegisterEncoder("duplicate-caller", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) {
return &duplicateCallerEncoder{Encoder: zapcore.NewJSONEncoder(config)}, nil
})
// it's reasonable to panic here, since the program can't initialize
if err != nil {
panic(err)
}
}
func main() {
cfg := zap.NewProductionConfig()
cfg.Encoding = "duplicate-caller"
logger, _ := cfg.Build()
logger.Info("this is info")
}
The above replicates the initialization of a production logger with your custom config.
For such a simple config, I prefer the init()
approach with zap.RegisterEncoder
. It makes it faster to refactor code, if needed, and/or if you place this in some other package to begin with. You can of course do the registration in main()
; or if you need additional customization, then you may use zap.New(zapcore.NewCore(myCustomEncoder, /* other args */))
You can see the full program in this playground: https://go.dev/play/p/YLDXbdZ-qZP
It outputs:
{"level":"info","ts":1257894000,"caller":"sandbox3965111040/prog.go:24","msg":"this is info","method":"sandbox3965111040/prog.go:24"}