diff --git a/slog/benchmarks/benchmarks_test.go b/slog/benchmarks/benchmarks_test.go index ce8262005..8cea9fa20 100644 --- a/slog/benchmarks/benchmarks_test.go +++ b/slog/benchmarks/benchmarks_test.go @@ -145,3 +145,55 @@ func BenchmarkAttrs(b *testing.B) { }) } } + +func BenchmarkWith(b *testing.B) { + for _, handler := range []struct { + name string + h slog.Handler + }{ + {"Text discard", slog.NewTextHandler(io.Discard)}, + {"JSON discard", slog.NewJSONHandler(io.Discard)}, + } { + logger := slog.New(handler.h) + b.Run(handler.name, func(b *testing.B) { + for _, call := range []struct { + name string + args []any + }{ + { + "no parameters", + []any{}, + }, + { + "1 key/value pair", + []any{ + "string", TestString, + }, + }, + { + "5 key/value pairs", + []any{ + "string", TestString, + "status", TestInt, + "duration", TestDuration, + "time", TestTime, + "error", TestError, + }, + }, + } { + b.Run(call.name, func(b *testing.B) { + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + l := logger + for pb.Next() { + l = logger.With(call.args...) + } + globalLogger = l + }) + }) + } + }) + } +} + +var globalLogger slog.Logger diff --git a/slog/logger.go b/slog/logger.go index 8f021e6f6..d7e38cac8 100644 --- a/slog/logger.go +++ b/slog/logger.go @@ -21,7 +21,7 @@ func init() { } // Default returns the default Logger. -func Default() *Logger { return defaultLogger.Load().(*Logger) } +func Default() Logger { return *defaultLogger.Load().(*Logger) } // SetDefault makes l the default Logger. // After this call, output from the log package's default Logger @@ -94,7 +94,7 @@ func (l *Logger) Handler() Handler { return l.handler } // The new Logger shares the old Logger's context. // The new Logger's handler is the result of calling WithAttrs on the receiver's // handler. -func (l *Logger) With(args ...any) *Logger { +func (l Logger) With(args ...any) Logger { var ( attr Attr attrs []Attr @@ -103,7 +103,7 @@ func (l *Logger) With(args ...any) *Logger { attr, args = argsToAttr(args) attrs = append(attrs, attr) } - c := l.clone() + c := l c.handler = l.handler.WithAttrs(attrs) return c } @@ -122,15 +122,15 @@ func (l *Logger) WithGroup(name string) *Logger { } // New creates a new Logger with the given non-nil Handler and a nil context. -func New(h Handler) *Logger { +func New(h Handler) Logger { if h == nil { panic("nil Handler") } - return &Logger{handler: h} + return Logger{handler: h} } // With calls Logger.With on the default logger. -func With(args ...any) *Logger { +func With(args ...any) Logger { return Default().With(args...) } @@ -211,7 +211,7 @@ func (l *Logger) ErrorCtx(ctx context.Context, msg string, args ...any) { // log is the low-level logging method for methods that take ...any. // It must always be called directly by an exported logging method // or function, because it uses a fixed call depth to obtain the pc. -func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) { +func (l Logger) log(ctx context.Context, level Level, msg string, args ...any) { if !l.Enabled(ctx, level) { return } @@ -231,7 +231,7 @@ func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) } // logAttrs is like [Logger.log], but for methods that take ...Attr. -func (l *Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) { +func (l Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) { if !l.Enabled(ctx, level) { return } diff --git a/slog/logger_test.go b/slog/logger_test.go index 2f04540c8..b8323ada6 100644 --- a/slog/logger_test.go +++ b/slog/logger_test.go @@ -498,3 +498,11 @@ func callerPC(depth int) uintptr { runtime.Callers(depth, pcs[:]) return pcs[0] } + +func TestNullLogger(t *testing.T) { + var logger Logger + enabled := logger.Enabled(context.Background(), LevelInfo) + if enabled { + t.Error("null Logger should disable logging") + } +}