【问题标题】:How to mock zap logger from ctrl "sigs.k8s.io/controller-runtime"?如何从 ctrl“sigs.k8s.io/controller-runtime”模拟 zap 记录器?
【发布时间】:2021-12-13 15:30:30
【问题描述】:
package logger

import (
    "bytes"
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    ctrl "sigs.k8s.io/controller-runtime"
)
var _ = Describe("Logger", func() {
    It("Test Default Log Level", func() {
        buf := &bytes.Buffer{}
        testLog := ctrl.Log.WithName("setup")
        SetLogger()
        

        testLog.Info("This is a test")
        Expect(buf.String(),"This is a test")
    })
})

这是SetLogger 函数,也用于生产:

package logger

import (
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/log/zap"
    ...
)

func SetLogger() {
    opts := zap.Options{
        Development:     developmentFlag,
        StacktraceLevel: stacktraceLevel,
        Level:           isLevelEnabler,
        Encoder:         logFmtEncoder,
    }
  ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
}

如何将testLog.Info 的输出更改为缓冲区?

【问题讨论】:

    标签: go logging go-zap


    【解决方案1】:

    如果你只对测试日志消息感兴趣,你可以使用钩子。

    特别是zap.Hooks 函数从可变数量的挂钩构造zap.Option。钩子只是一个func(entry zapcore.Entry) error,您可以使用它来拦截条目并将其消息写入缓冲区。

    要将此zap.Option 设置到您的sigs.k8s.io 记录器中,请将其设置为ZapOpts 字段:

        opts := k8szap.Options{
            // ...
            ZapOpts: []zap.Option{
                zap.Hooks(func(entry zapcore.Entry) error {
                    buf.WriteString(entry.Message)
                    return nil
                }),
            },
        }
    

    因此,由于您需要访问缓冲区,因此可以将其作为参数传递给 SetLogger 函数:

    func SetLogger(buf *bytes.Buffer) {
        opts := zap.Options{
            Development:     developmentFlag,
            StacktraceLevel: stacktraceLevel,
            Level:           isLevelEnabler,
            Encoder:         logFmtEncoder,
    
            // here 'zap' selector is 'go.uber.org/zap'
            ZapOpts: []zap.Option{
                zap.Hooks(func(entry zapcore.Entry) error {
                    buf.WriteString(entry.Message)
                    return nil
                }),
            },
        }
        // here I call 'k8szap' selector the package 'sigs.k8s.io/controller-runtime/pkg/log/zap'
        ctrl.SetLogger(k8szap.New(k8szap.UseFlagOptions(&opts)))
    }
    

    然后在你的测试函数中:

        It("Test Default Log Level", func() {
            buf := &bytes.Buffer{}
            testLog := ctrl.Log.WithName("setup")
    
            // pass buffer to SetLogger
            SetLogger(buf)
            
    
            testLog.Info("This is a test")
            Expect(buf.String(), "This is a test")
        })
    

    最小示例(在操场上下载包时可能会超时):https://play.golang.org/p/oBN3SHFKVC8

    【讨论】:

    • @blackGrren - 再次感谢您的支持,我还有 2 个问题:在真实情况下(不在测试中)我需要将什么传递给 buf *bytes.Buffer 而不是在测试中?设置记录器后是否可以设置它或者它必须处于初始化阶段?
    • @user1365697 如果您使用指针 *bytes.Buffer 从技术上讲,您可以保留变量并随时为其分配一些其他值,但是如果出现错误,这已经成熟你的程序有并发代码。在初始化期间设置它可能是一个更好的选择
    • @blackGrren - 真实情况下的 *bytes.Buffer 应该是什么?当我从生产调用中调用它时
    • @user1365697 好吧,这取决于想要做什么。为什么在生产中需要这个?如果您的目标是将输出重定向到其他地方,更好的选择可能是zapcore.NewTee,类似于here。如果这只是为了测试,你最好稍微重构一下代码,因为测试的东西不应该溢出到生产代码中......但这有点超出你的问题范围,所以很难回答
    • @blackGrren - 我在生产中不需要它,所以我应该将什么传递给 SetLogger,因为我向 SetLogger 添加了新的参数 buf *bytes.Buffer 所以它也在生产中