diff --git a/log/log.go b/log/log.go index 155e19969..5cf1b9abd 100644 --- a/log/log.go +++ b/log/log.go @@ -23,14 +23,32 @@ func With(logger Logger, keyvals ...interface{}) Logger { if w, ok := logger.(Wither); ok { return w.With(keyvals...) } - return LoggerFunc(func(kvs ...interface{}) error { - // Limiting the capacity of the first argument to append ensures that - // a new backing array is created if the slice must grow. Using the - // extra capacity without copying risks a data race that would violate - // the Logger interface contract. - n := len(keyvals) - return logger.Log(append(keyvals[:n:n], kvs...)...) - }) + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + n := len(keyvals) + return &withLogger{ + logger: logger, + keyvals: keyvals[:n:n], + } +} + +type withLogger struct { + logger Logger + keyvals []interface{} +} + +func (l *withLogger) Log(keyvals ...interface{}) error { + return l.logger.Log(append(l.keyvals, keyvals...)...) +} + +func (l *withLogger) With(keyvals ...interface{}) Logger { + n := len(l.keyvals) + len(keyvals) + return &withLogger{ + logger: l.logger, + keyvals: append(l.keyvals, keyvals...)[:n:n], + } } // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If @@ -49,3 +67,10 @@ func (f LoggerFunc) Log(keyvals ...interface{}) error { type Wither interface { With(keyvals ...interface{}) Logger } + +// NewDiscardLogger returns a logger that does not log anything. +func NewDiscardLogger() Logger { + return LoggerFunc(func(...interface{}) error { + return nil + }) +} diff --git a/log/log_test.go b/log/log_test.go index f12721266..837a6f4de 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -78,3 +78,46 @@ func TestWithConcurrent(t *testing.T) { } } } + +func BenchmarkDiscard(b *testing.B) { + logger := log.NewDiscardLogger() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + logger.Log("k", "v") + } +} + +func BenchmarkOneWith(b *testing.B) { + logger := log.NewDiscardLogger() + logger = log.With(logger, "k", "v") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + logger.Log("k", "v") + } +} + +func BenchmarkTwoWith(b *testing.B) { + logger := log.NewDiscardLogger() + for i := 0; i < 2; i++ { + logger = log.With(logger, "k", "v") + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + logger.Log("k", "v") + } +} + +func BenchmarkTenWith(b *testing.B) { + logger := log.NewDiscardLogger() + for i := 0; i < 10; i++ { + logger = log.With(logger, "k", "v") + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + logger.Log("k", "v") + } +}