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