英文:
Why custom encoding is lost after calling logger.With in Uber Zap?
问题
根据您提供的代码,您替换了uber-zap日志记录器的编码器,以便在每个日志条目前添加一个SystemD友好的错误级别(<LEVEL>)。但是,当您在使用附加字段(With(fields ...Field))的日志记录器之后,自定义的前缀消失了。
您得到的输出是:
<6> 1.640656130756576e+09 info this is info
<7> 1.640656130756611e+09 debug this is debug
<4> 1.640656130756615e+09 warn this is warn
1.6406561307566311e+09 info this does not have the prefix :( {"foo": "bar"}
您想知道自己做错了什么。
根据您提供的代码,问题可能出在以下几个地方:
-
在
main函数中,您在创建新的logger实例时,使用了logger = logger.With(zap.String("foo", "bar"))。这会创建一个新的logger实例,并且新的实例不会继承自定义的编码器。您可以尝试将其更改为logger = logger.With(zap.String("foo", "bar")),以便在现有的logger实例上添加字段,而不是创建一个新的实例。 -
在
EncodeEntry方法中,您调用了嵌入的编码器的EncodeEntry方法来保持原始的编码格式。但是,这可能会导致附加字段的编码不包含自定义前缀。您可以尝试将EncodeEntry方法中的以下行更改为:
consolebuf, err := e.Encoder.EncodeEntry(entry, fields)
更改为:
consolebuf, err := e.Encoder.EncodeEntry(entry, nil)
这样可以确保附加字段的编码不包含自定义前缀。
请尝试对这些地方进行更改,并查看是否解决了您的问题。
英文:
(based on this question: https://stackoverflow.com/questions/70489167/uber-zap-logger-how-to-prepend-every-log-entry-with-a-string)
I replaced the Encoder of my uber-zap logger with a custom one to prepend every log entry with a SystemD-friendly error level (<LEVEL>), but now after I use the logger with additional fields (With(fields ...Field)), the custom prepending is gone:
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
func getConfig() zap.Config {
// your current config options
return zap.NewProductionConfig()
}
type prependEncoder struct {
// embed a zapcore encoder
// this makes prependEncoder implement the interface without extra work
zapcore.Encoder
// zap buffer pool
pool buffer.Pool
}
// EncodeEntry implementing only EncodeEntry
func (e *prependEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// new log buffer
buf := e.pool.Get()
// prepend the JournalD prefix based on the entry level
buf.AppendString(e.toJournaldPrefix(entry.Level))
buf.AppendString(" ")
// calling the embedded encoder's EncodeEntry to keep the original encoding format
consolebuf, err := e.Encoder.EncodeEntry(entry, fields)
if err != nil {
return nil, err
}
// just write the output into your own buffer
_, err = buf.Write(consolebuf.Bytes())
if err != nil {
return nil, err
}
return buf, nil
}
// some mapper function
func (e *prependEncoder) toJournaldPrefix(lvl zapcore.Level) string {
switch lvl {
case zapcore.DebugLevel:
return "<7>"
case zapcore.InfoLevel:
return "<6>"
case zapcore.WarnLevel:
return "<4>"
}
return ""
}
func main() {
cfg := getConfig()
// constructing our prependEncoder with a ConsoleEncoder using your original configs
enc := &prependEncoder{
Encoder: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
pool: buffer.NewPool(),
}
logger := zap.New(
zapcore.NewCore(
enc,
os.Stdout,
zapcore.DebugLevel,
),
// this mimics the behavior of NewProductionConfig.Build
zap.ErrorOutput(os.Stderr),
)
logger.Info("this is info")
logger.Debug("this is debug")
logger.Warn("this is warn")
logger = logger.With(zap.String("foo", "bar"))
logger.With(zap.String("foo", "bar")).Info("this does not have the prefix :(")
}
The output I get is:
<6> 1.640656130756576e+09 info this is info
<7> 1.640656130756611e+09 debug this is debug
<4> 1.640656130756615e+09 warn this is warn
1.6406561307566311e+09 info this does not have the prefix :( {"foo": "bar"}
What am I doing wrong?
答案1
得分: 2
你还需要实现zapcore.Encoder接口中的Clone()方法。如果你希望保持父记录器不变,你需要构建一个实际的克隆对象,可能具有相同的配置,所以你可能想将其存储为一个字段:
type prependEncoder struct {
zapcore.Encoder
pool buffer.Pool
cfg zapcore.EncoderConfig
}
func (e *prependEncoder) Clone() zapcore.Encoder {
return &prependEncoder{
// 使用基本配置克隆编码器
Encoder: zapcore.NewConsoleEncoder(e.cfg),
pool: buffer.NewPool(),
cfg: e.cfg,
}
}
如果你不实现它,当调用logger.Clone()时,运行的方法将是嵌入的zapcore.Encoder上声明的下一个最浅的方法,这个方法将不再具有你自定义的EncodeEntry。
现在运行以下代码:
logger.Info("this is info")
logger.Debug("this is debug")
logger.Warn("this is warn")
child := logger.With(zap.String("foo", "bar"))
logger.Warn("original")
child.Info("new one")
输出结果为:
<6> INFO this is info
<7> DEBUG this is debug
<4> WARN this is warn
cloning...
<4> WARN original
<6> INFO new one {"foo": "bar"}
英文:
You have to also implement Clone() from the zapcore.Encoder interface. If you wish to keep the parent logger unaltered, you have to construct an actual clone — possibly with the same config, so you might want to store it as a field:
type prependEncoder struct {
zapcore.Encoder
pool buffer.Pool
cfg zapcore.EncoderConfig
}
func (e *prependEncoder) Clone() zapcore.Encoder {
return &prependEncoder{
// cloning the encoder with the base config
Encoder: zapcore.NewConsoleEncoder(e.cfg),
pool: buffer.NewPool(),
cfg: e.cfg,
}
}
If you don't implement it, the method that runs is the next shallowest one when calling logger.Clone(), which is the Clone() declared on the embedded zapcore.Encoder. That one then doesn't have your custom EncodeEntry anymore.
Now running the following:
logger.Info("this is info")
logger.Debug("this is debug")
logger.Warn("this is warn")
child := logger.With(zap.String("foo", "bar"))
logger.Warn("original")
child.Info("new one")
Outputs:
<6> INFO this is info
<7> DEBUG this is debug
<4> WARN this is warn
cloning...
<4> WARN original
<6> INFO new one {"foo": "bar"}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论