英文:
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"}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论