如何访问 zap Hooks 中的字段?

How to access Fields in zap Hooks?

如何在 uber-zap 的挂钩中访问有关日志记录事件的完整信息?

例如,我试图将 zapcore.Field 添加到日志记录事件中,但它没有显示在 zapcore.Entry.

如果不可能,我至少可以以某种方式获得完整格式的字符串吗?目标是在出现错误时发送 email/automated message/Sentry/etc。

package main

import (
    "log"

    "github.com/davecgh/go-spew/spew"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
    prodLogger, err := zap.NewProduction(zap.Hooks(func(entry zapcore.Entry) error {
        if entry.Level == zapcore.ErrorLevel {
            spew.Dump(entry) // fancy console printer
        }

        return nil
    }))

    if err != nil {
        log.Fatal(err)
    }

    prodLogger.
        Named("logger_name").
        Error("something happened", zap.String("foo", "bar"))
}

输出 - 没有 foobar 的痕迹:

{"level":"error","ts":1640722252.899601,"logger":"logger_name","caller":"awesomep2/main.go:23","msg":"something happened","foo":"bar","stacktrace":"main.main\n\t/Users/xxx/GitHub/awesomep2/main.go:23\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"}
(zapcore.Entry) {
 Level: (zapcore.Level) error,
 Time: (time.Time) 2021-12-28 13:10:52.899601 -0700 MST m=+0.000629089,
 LoggerName: (string) (len=11) "logger_name",
 Message: (string) (len=18) "something happened",
 Caller: (zapcore.EntryCaller) /Users/xxx/GitHub/awesomep2/main.go:23,
 Stack: (string) (len=103) "main.main\n\t/Users/xxx/GitHub/awesomep2/main.go:23\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"
}

字段在 Zap 挂钩中不可用。 zap.Hooks 的文档明确说明了这一点:

[...] Hooks are useful for simple side effects, like capturing metrics for the number of emitted logs. More complex side effects, including anything that requires access to the Entry's structured fields, should be implemented as a zapcore.Core instead. [...]

因此,要使用 go-spew 转储日志,您需要一个自定义内核。您有两个主要选择。

带有自定义编码器的自定义内核

这样做的好处是可以进行更多自定义。

条目的字段在 zapcore.Encoder.EncodeEntry 中可用。像往常一样,策略是将 zapcore.Encoder 嵌入到您的结构中并重新实现 EncodeEntry:

type spewDumpEncoder struct {
    zapcore.Encoder
}

func (e *spewDumpEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
    if entry.Level == zapcore.ErrorLevel {
        spew.Dump(entry, fields)
    }
    return e.Encoder.EncodeEntry(entry, fields)
}

如果您计划使用结构化日志记录,请记住也实施 Clone()

带有 Write

的自定义核心

这样做的好处是初始化更简单。

与第一个选项类似,zapcore.Core也是一个接口,所以你可以通过嵌入到你的结构中来实现它,并且只需要重新实现Write:

type MyCore struct {
    zapcore.Core
}

func (c *MyCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
    if c.Enabled(entry.Level) {
        return checked.AddCore(entry, c)
    }
    return checked
}

func (c *MyCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
    if entry.Level == zapcore.ErrorLevel {
        spew.Dump(entry, fields)
    }
    return c.Core.Write(entry, fields)
}

并通过从默认的 zap 记录器中获取现有核心来实例化它:

    l, _ := zap.NewProduction()
    logger := zap.New(&MyCore{Core: l.Core()})