Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions pkg/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,34 @@ func AddSentry(l logr.Logger, opts sentry.ClientOptions, tags map[string]string)
return AddSink(l, WithSentry(opts, tags))
}

// AddSink extends an existing logr.Logger with a new sink. It returns the new
// logr.Logger, a cleanup function, and an error.
func AddSink(l logr.Logger, sink logConfig) (logr.Logger, func() error, error) {
// AddSink extends an existing logr.Logger with a new sink. It returns the new logr.Logger, a cleanup function, and an
// error.
//
// The new sink will not inherit any of the existing logger's key-value pairs. Key-value pairs can be added to the new
// sink specifically by passing them to this function.
func AddSink(l logr.Logger, sink logConfig, keysAndValues ...any) (logr.Logger, func() error, error) {
if sink.err != nil {
return l, nil, sink.err
}

// New key-value pairs cannot be ergonomically added directly to cores. logr has code to do it, but that code is not
// exported. Rather than replicating it ourselves, we indirectly use it by creating a temporary logger for the new
// core, adding the key-value pairs to the temporary logger, and then extracting the temporary logger's modified
// core.
Comment on lines +149 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the "non-ergonomic way" you're referring to the Core.With method?

newSinkLogger := zapr.NewLogger(zap.New(sink.core))
newSinkLogger = newSinkLogger.WithValues(keysAndValues...)
newCoreLogger, err := getZapLogger(newSinkLogger)
if err != nil {
return l, nil, fmt.Errorf("error setting up new key-value pairs: %w", err)
}
newSinkCore := newCoreLogger.Core()

zapLogger, err := getZapLogger(l)
if err != nil {
return l, nil, errors.New("unsupported logr implementation")
}
zapLogger = zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewTee(core, sink.core)
return zapcore.NewTee(core, newSinkCore)
}))
return zapr.NewLogger(zapLogger), firstErrorFunc(zapLogger.Sync, sink.cleanup), nil
}
Expand Down
23 changes: 23 additions & 0 deletions pkg/log/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,29 @@ func TestAddSink(t *testing.T) {
assert.Contains(t, buf2.String(), "line 2")
}

func TestAddSink_WithKeyValuePairs(t *testing.T) {
// Arrange: Create a logger with a key-value pair
var buf1 bytes.Buffer
logger, cleanup := New("service-name", WithConsoleSink(&buf1))
t.Cleanup(func() { _ = cleanup })
logger = logger.WithValues("sink 1 key", "sink 1 value")

// Arrange: Add a second sink with a new key-value pair
var buf2 bytes.Buffer
logger, flush, err := AddSink(logger, WithConsoleSink(&buf2), "sink 2 key", "sink 2 value")
require.NoError(t, err)

// Act
logger.Info("something")
require.NoError(t, flush())

// Assert: Confirm that each sink received only its own key-value pair
assert.Contains(t, buf1.String(), "sink 1")
assert.NotContains(t, buf1.String(), "sink 2")
assert.Contains(t, buf2.String(), "sink 2")
assert.NotContains(t, buf2.String(), "sink 1")
}

func TestStaticLevelSink(t *testing.T) {
var buf1, buf2 bytes.Buffer
l1 := zap.NewAtomicLevel()
Expand Down
Loading