英文:
How can I duplicate entry keys and show it in the same log with Uber Zap?
问题
我想要将caller
这个键复制一份,用另一个键名method
,并在日志中同时显示两个键...
{"level":"error", "caller": "testing/testing.go:1193", "method": "testing/testing.go:1193", "message": "foo"}
有什么想法吗?
英文:
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?
答案1
得分: 2
你不能更改zapcore.Entry
的字段。你可以更改它的编组方式,但是在结构体中添加幽灵字段是一种糟糕的hack。你可以使用自定义编码器,并将调用者的副本作为新的字符串项附加到[]zapcore.Field
中。特别是,默认的JSON编码器输出是从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) {
// 将项附加到字段列表中
fields = append(fields, zap.String("method", entry.Caller.TrimmedPath()))
return e.Encoder.EncodeEntry(entry, fields)
}
请注意,上述代码实现了Encoder.Clone()
。详细信息请参见:https://stackoverflow.com/questions/70502026/why-custom-encoding-is-lost-after-calling-logger-with-in-uber-zap/70503730#70503730
然后,你可以通过构建一个新的Zap核心或注册自定义编码器来使用它。注册的构造函数将JSONEncoder
嵌入到你的自定义编码器中,这是生产日志记录器的默认编码器:
func init() {
// name可以自定义
err := zap.RegisterEncoder("duplicate-caller", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) {
return &duplicateCallerEncoder{Encoder: zapcore.NewJSONEncoder(config)}, nil
})
// 如果出错,panic是合理的,因为程序无法初始化
if err != nil {
panic(err)
}
}
func main() {
cfg := zap.NewProductionConfig()
cfg.Encoding = "duplicate-caller"
logger, _ := cfg.Build()
logger.Info("this is info")
}
上述代码复制了使用自定义配置初始化生产日志记录器的过程。
对于这样简单的配置,我更喜欢使用init()
方法和zap.RegisterEncoder
。如果需要的话,这样可以更快地重构代码,或者如果一开始就将其放在其他包中。当然,你也可以在main()
函数中进行注册;或者如果需要额外的自定义,则可以使用zap.New(zapcore.NewCore(myCustomEncoder, /* other args */))
。
你可以在这个playground中查看完整的程序:https://go.dev/play/p/YLDXbdZ-qZP
它的输出是:
{"level":"info","ts":1257894000,"caller":"sandbox3965111040/prog.go:24","msg":"this is info","method":"sandbox3965111040/prog.go:24"}
英文:
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: https://stackoverflow.com/questions/70502026/why-custom-encoding-is-lost-after-calling-logger-with-in-uber-zap/70503730#70503730
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"}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论