1
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?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
user1365697
  • 5,819
  • 15
  • 60
  • 96

1 Answers1

1

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

blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • @blackGrren - thanks again for the support , iI still have 2 questions : what do I need to pass in the real case ( not in test ) to the buf *bytes.Buffer and not in the testing ? is it possible ot set it after the logger was set or it must be in the init phase ? – user1365697 Oct 28 '21 at 13:04
  • @user1365697 if you use a pointer `*bytes.Buffer` technically you *can* keep the variable around and assign some other value to it at any time, but this is ripe for bugs if your program has concurrent code. Setting it during init may be a better option – blackgreen Oct 28 '21 at 13:07
  • @blackGrren - what should be the *bytes.Buffer in the real case ? when I call it from the production call – user1365697 Oct 28 '21 at 13:11
  • @user1365697 well, that depends on what **you** want to do. Why do you need this in production? If your goal is to redirect the output somewhere else, a better option may be `zapcore.NewTee`, similar to what happens [here](https://stackoverflow.com/questions/68472667/how-to-log-to-stdout-or-stderr-based-on-log-level-using-uber-go-zap/68476472#68476472). If this is only for testing you might be better off refactoring the code a little bit, as testing stuff should not spill into production code... but this is kinda out of the scope of your question so it's hard to answer – blackgreen Oct 28 '21 at 13:17
  • @ blackGrren - i dont need it in production so what should I pass to the SetLogger because I added new param buf *bytes.Buffer to the SetLogger so it also in production – user1365697 Oct 28 '21 at 13:20
  • @user1365697 what I would do, probably, I would separate the code in `SetLogger` function so that it takes k8s `zap.Options` as argument instead; and init the options somewhere else. Then in production you init the options as you do know, and in testing you can change the opts by setting `ZapOpts` field before passing it to `SetLogger` – blackgreen Oct 28 '21 at 13:26