Uber Zap Logger:如何在每个日志条目前添加一个字符串

huangapple go评论99阅读模式
英文:

Uber Zap Logger: how to prepend every log entry with a string

问题

我正在使用我的应用作为 SystemD 服务,并且需要在每条消息前面添加一个 JournalD 的条目级别 <LEVEL>,例如:

<6> 这是信息
<7> 这是调试
<4> 这是警告

否则,JournalD 将把所有条目视为相同级别,而我想要利用其高级功能仅显示特定级别的日志。

如何使用 uber-zap 库在每个日志条目前面添加正确的级别标签(例如对于 Info 级别,它将是 <6>)?

编辑:这是我的日志记录器配置的相关部分:

    var config zap.Config

	if production {
		config = zap.NewProductionConfig()
		config.Encoding = `console`
		config.EncoderConfig.TimeKey = "" // 不需要时间戳,因为 SystemD 会添加时间戳
	} else {
		config = zap.NewDevelopmentConfig()
	}

	config.DisableStacktrace = true
	config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 使用颜色
	config.OutputPaths = []string{"stdout"}
英文:

I am using my app as a SystemD service and need to prepend every message with an entry level &lt;LEVEL&gt; for JournalD like:

&lt;6&gt; this is info
&lt;7&gt; this is debug
&lt;4&gt; this is warning

Otherwise, JournalD treats all the entries the same level and I want to use its advanced capabilities for displaying logs only of certain level.

How can I prepend every log entry with the correct level label (like for Info it would be &lt;6&gt;) with uber-zap library?

EDIT: This is the relevant part of my logger configuration:

    var config zap.Config

	if production {
		config = zap.NewProductionConfig()
		config.Encoding = `console`
		config.EncoderConfig.TimeKey = &quot;&quot; // no time as SystemD adds timestamp
	} else {
		config = zap.NewDevelopmentConfig()
	}

	config.DisableStacktrace = true
	config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // colors
	config.OutputPaths = []string{&quot;stdout&quot;}

答案1

得分: 2

你可以使用一个自定义编码器来嵌入zapcore.Encoder

嵌入编码器可以让你使用相同的配置实现所有方法,然后你可以只实现EncodeEntry方法并添加你需要的额外逻辑。

注意: 如果你计划使用结构化日志记录(例如logger.With()),你仍然需要实现Clone()方法。更多信息请参考:https://stackoverflow.com/questions/70502026

回到你的主要问题,这是一个可工作的示例;请查看代码中的注释以获取额外的解释:

type prependEncoder struct {
    // 嵌入一个zapcore编码器
    // 这样prependEncoder就可以在不额外工作的情况下实现接口
	zapcore.Encoder

    // zap缓冲池
	pool buffer.Pool
}

// 只实现EncodeEntry方法
func (e *prependEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
	// 新的日志缓冲区
    buf := e.pool.Get()

    // 根据日志级别添加JournalD前缀
	buf.AppendString(e.toJournaldPrefix(entry.Level))
	buf.AppendString(" ")

    // 调用嵌入的编码器的EncodeEntry方法以保持原始的编码格式
	consolebuf, err := e.Encoder.EncodeEntry(entry, fields)
	if err != nil {
		return nil, err
	}

    // 将输出写入自己的缓冲区
	_, err = buf.Write(consolebuf.Bytes())
	if err != nil {
		return nil, err
	}
	return buf, nil
}

// 一些映射函数
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 ""
}

然后,使用使用自定义编码器构建一个使用该自定义编码器的自定义核心的记录器。你可以使用与当前选项相似的选项来初始化嵌入字段。

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/buffer"
	"go.uber.org/zap/zapcore"
	"os"
)

func getConfig() zap.Config {
    // 你当前的配置选项
	return config
}

func main() {
	cfg := getConfig()

    // 使用ConsoleEncoder和原始配置构建prependEncoder
	enc := &prependEncoder{
		Encoder: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
		pool:    buffer.NewPool(),
	}

	logger := zap.New(
        zapcore.NewCore(
		    enc,
		    os.Stdout,
		    zapcore.DebugLevel,
	    ),
        // 这里模拟NewProductionConfig.Build的行为
		zap.ErrorOutput(os.Stderr), 
	)

	logger.Info("this is info")
    logger.Debug("this is debug")
    logger.Warn("this is warn")
}

测试运行输出(INFO以蓝色打印,DEBUG以粉色打印,WARN以黄色打印,与你的zapcore.CapitalColorLevelEncoder相符):

<6> INFO        this is info
<7> DEBUG       this is debug
<4> WARN        this is warn
英文:

You can use a custom encoder that embeds a zapcore.Encoder.

Embedding the encoder gives you the implementation of all methods "for free" with the same configuration you have now. Then you can implement only EncodeEntry with the additional logic you require.

NOTE: You still have to implement Clone() if you plan to use structured logging, e.g. logger.With(). More info: https://stackoverflow.com/questions/70502026

Back to your main question, this is a working example; see the comments in code for additional explanation:

type prependEncoder struct {
    // embed a zapcore encoder
    // this makes prependEncoder implement the interface without extra work
	zapcore.Encoder

    // zap buffer pool
	pool buffer.Pool
}

// 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(&quot; &quot;)

    // calling the embedded encoder&#39;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 &quot;&lt;7&gt;&quot;
	case zapcore.InfoLevel:
		return &quot;&lt;6&gt;&quot;
	case zapcore.WarnLevel:
		return &quot;&lt;4&gt;&quot;
	}
	return &quot;&quot;
}

Later you construct a logger with a custom core that uses the custom encoder. You initialize the embedded field with the same encoder you are using now. The options you see below mimic the options you currently have.

package main

import (
	&quot;go.uber.org/zap&quot;
	&quot;go.uber.org/zap/buffer&quot;
	&quot;go.uber.org/zap/zapcore&quot;
	&quot;os&quot;
)

func getConfig() zap.Config {
    // your current config options
	return config
}

func main() {
	cfg := getConfig()

    // constructing our prependEncoder with a ConsoleEncoder using your original configs
	enc := &amp;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(&quot;this is info&quot;)
    logger.Debug(&quot;this is debug&quot;)
    logger.Warn(&quot;this is warn&quot;)
}

Test run output (INFO is printed in blue, DEBUG in pink and WARN in yellow as per your zapcore.CapitalColorLevelEncoder):

&lt;6&gt; INFO        this is info
&lt;7&gt; DEBUG       this is debug
&lt;4&gt; WARN        this is warn

huangapple
  • 本文由 发表于 2021年12月27日 03:12:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/70489167.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定