Search code examples
gologginggo-zap

How to mock zap logger from ctrl "sigs.k8s.io/controller-runtime"?


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")
    })
})

And this is the SetLogger function, which is used also in production:

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)))
}

How I can change the output of the testLog.Info to buffer?


Solution

  • If you are only interested in testing the log message, you can use a hook.

    In particular zap.Hooks function constructs a zap.Option from a variable number of hooks. A hook is just a func(entry zapcore.Entry) error which you can use to intercept the entry and write its message to the buffer.

    To set this zap.Option into your sigs.k8s.io logger, you set it to the ZapOpts field:

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

    So since you need access to the buffer, you can pass it as argument to the SetLogger function:

    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)))
    }
    

    And then in your test function:

        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")
        })
    

    Minimal example (it may timeout when downloading the packages in the playground): https://play.golang.org/p/oBN3SHFKVC8