为什么在 Uber Zap 中调用 logger.With 后自定义编码丢失?

Why custom encoding is lost after calling logger.With in Uber Zap?

(基于这个问题:

我用一个自定义编码器替换了我的 uber-zap 记录器的编码器,以便在每个日志条目前添加一个 SystemD 友好的错误级别 (<LEVEL>),但现在我使用带有附加字段的记录器 ( With(fields ...Field)), 自定义前缀不见了:

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 :(")
}

我得到的输出是:

<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"}

我做错了什么?

您还必须从 zapcore.Encoder 接口实现 Clone()。如果您希望保持父记录器不变,您必须构建一个实际的克隆 — 可能具有相同的配置,因此您可能希望将其存储为一个字段:

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,
    }
}

如果不实现,运行的方法是调用logger.Clone()时下一个最浅的方法,也就是嵌入的zapcore.Encoder上声明的Clone()。那一个就没有你的习惯了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"}