如何在单元测试中正确捕获 Zap Logger 的输出

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

How to properly capture zap logger output in unit tests

问题

根据zap.NewDevelopmentConfig()zap.NewProductionConfig()的配置,我假设zap将日志写入stderr。然而,在单元测试中,我似乎无法捕获输出。

我有以下的captureOutput函数:

func captureOutput(f func()) string {
	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}

	stdout := os.Stdout
	os.Stdout = w
	defer func() {
		os.Stdout = stdout
	}()

	stderr := os.Stderr
	os.Stderr = w
	defer func() {
		os.Stderr = stderr
	}()

	f()
	w.Close()

	var buf bytes.Buffer
	io.Copy(&buf, r)

	return buf.String()
}

它无法捕获zap的输出,但可以捕获fmt.Println(...)的输出:

func TestZapCapture(t *testing.T) {
	auditor, _ := zap.NewProduction()
	output := captureOutput(func() {
		auditor.Info("hi")
	})

	assert.NotEmpty(t, output)
	// 无法捕获输出
}

func TestFmtCapture(t *testing.T) {
	output := captureOutput(func() {
		fmt.Println("hi")
	})

	assert.NotEmpty(t, output)
	// 成功捕获输出
}

我知道可以使用zap的observer来处理这种情况,但我的真实用例是测试一个高度修改的zap日志记录器,因此测试一个新的zap.Core会失去意义。有什么最好的方法来捕获输出?

英文:

Based off the configurations for zap.NewDevelopmentConfig() and zap.NewProductionConfig(), I've assumed that zap writes logs to stderr. However, I can't seem to capture the output in unit tests.

I have the following captureOutput func:

func captureOutput(f func()) string {
	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}

	stdout := os.Stdout
	os.Stdout = w
	defer func() {
		os.Stdout = stdout
	}()

	stderr := os.Stderr
	os.Stderr = w
	defer func() {
		os.Stderr = stderr
	}()

	f()
	w.Close()

	var buf bytes.Buffer
	io.Copy(&buf, r)

	return buf.String()
}

It fails to capture zap output but does manage to grab output from fmt.Println(...):

func TestZapCapture(t *testing.T) {
	auditor, _ := zap.NewProduction()
	output := captureOutput(func() {
		auditor.Info("hi")
	})

	assert.NotEmpty(t, output)
	//fails to captures output
}

func TestFmtCapture(t *testing.T) {
	output := captureOutput(func() {
		fmt.Println("hi")
	})

	assert.NotEmpty(t, output)
	//successfully captures output
}

I'm aware of using the zap observer for situations like this but my real use case is to test a highly modified zap logger so testing a new zap.Core would defeat the purpose. Whats the best way to capture that output?

答案1

得分: 2

测试是否记录了日志

使用zapcore.NewTee。在你的单元测试中,你可以实例化一个日志记录器,其核心由你自己高度修改的核心和观察到的核心组成。观察到的核心将接收日志条目,因此你可以断言单个字段是否符合你的预期(级别、消息、字段等)。

func main() {
    // 一些任意的自定义核心日志记录器
	mycore := zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		os.Stderr,
		zapcore.InfoLevel,
	)
    // 测试核心
	observed, logs := observer.New(zapcore.InfoLevel)
	
    // 使用两个核心进行组合的新日志记录器
	logger := zap.New(zapcore.NewTee(mycore, observed))

	logger.Error("foo")

    entry := logs.All()[0]
	fmt.Println(entry.Message == "foo")            // true
    fmt.Println(entry.Level == zapcore.ErrorLevel) // true
}

测试最终的日志格式

在这种情况下,你希望将日志记录器的输出导向任意的写入器。你可以使用zap.CombineWriteSyncers来实现这一点,并将其作为你自定义核心的依赖注入。

// 这段代码将放在你的主要代码中
func NewCustomLogger(pipeTo io.Writer) zapcore.Core {
	return zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		zap.CombineWriteSyncers(os.Stderr, zapcore.AddSync(pipeTo)),
		zapcore.InfoLevel,
	)
}

func TestLogger(t *testing.T) {
	b := &bytes.Buffer{}
    // 在测试代码中使用任意的写入器调用构造函数
	mycore := NewCustomLogger(b)

	logger := zap.New(mycore)
	logger.Error("foo")

	fmt.Println(b.String()) // {"level":"error","ts":1639813360.853494,"msg":"foo"}
}
英文:

Test that messages are logged at all

Use zapcore.NewTee. In your unit tests, you instantiate a logger whose core is comprised of your own highly modified core and the observed core tee'd together. The observed core will receive the log entries, so you can assert that single fields are what you expect (level, message, fields, etc.)

func main() {
    // some arbitrary custom core logger
	mycore := zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		os.Stderr,
		zapcore.InfoLevel,
	)
    // test core
	observed, logs := observer.New(zapcore.InfoLevel)
	
    // new logger with the two cores tee'd together
	logger := zap.New(zapcore.NewTee(mycore, observed))

	logger.Error("foo")

    entry := logs.All()[0]
	fmt.Println(entry.Message == "foo")            // true
    fmt.Println(entry.Level == zapcore.ErrorLevel) // true
}

Test the final log format

In this case you want to pipe the logger output to arbitrary writers. You can achieve this with zap.CombineWriteSyncers and inject this as a dependency of your custom core.

// this would be placed in your main code
func NewCustomLogger(pipeTo io.Writer) zapcore.Core {
	return zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		zap.CombineWriteSyncers(os.Stderr, zapcore.AddSync(pipeTo)),
		zapcore.InfoLevel,
	)
}

func TestLogger(t *testing.T) {
	b := &bytes.Buffer{}
    // call the constructor from your test code with the arbitrary writer
	mycore := NewCustomLogger(b)

	logger := zap.New(mycore)
	logger.Error("foo")

	fmt.Println(b.String()) // {"level":"error","ts":1639813360.853494,"msg":"foo"}
}

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

发表评论

匿名网友

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

确定