From fa6bbc49545bf5b9acd1aeb8c0f6e90ff8b9d890 Mon Sep 17 00:00:00 2001 From: Billy Lynch <1844673+wlynch@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:20:42 -0500 Subject: [PATCH] Fix duplicate log keys from context. (#34) Because we were using attr.With, we were setting the logger attributes, and then appending the log context attributes again later for any contextf functions. This changes the behavior to use the context for any log.Info functions. --- go.sum | 0 handler.go | 3 ++- logger.go | 24 ++++++++++++++++++---- logger_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 go.sum diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/handler.go b/handler.go index 46fe071..ae48847 100644 --- a/handler.go +++ b/handler.go @@ -6,9 +6,10 @@ import ( ) var ( - ctxKey = struct{}{} + ctxKey = key{} ) +type key struct{} type ctxVal map[string]any // With returns a new context with the given values. diff --git a/logger.go b/logger.go index 9798896..8264691 100644 --- a/logger.go +++ b/logger.go @@ -30,10 +30,6 @@ func NewLoggerWithContext(ctx context.Context, l *slog.Logger) *Logger { if l == nil { l = slog.New(NewHandler(slog.Default().Handler())) } - // Attach any existing context values onto the logger. - for k, v := range get(ctx) { - l = l.With(k, v) - } return &Logger{ ctx: ctx, Logger: *l, @@ -72,6 +68,11 @@ func (l *Logger) context() context.Context { return l.ctx } +// Infof logs at LevelInfo with the given format and arguments. +func (l *Logger) Info(format string, args ...any) { + wrap(l.context(), l, slog.LevelInfo, format, args...) +} + // Infof logs at LevelInfo with the given format and arguments. func (l *Logger) Infof(format string, args ...any) { wrapf(l.context(), l, slog.LevelInfo, format, args...) @@ -82,6 +83,11 @@ func (l *Logger) InfoContextf(ctx context.Context, format string, args ...any) { wrapf(ctx, l, slog.LevelInfo, format, args...) } +// Warn logs at LevelWarn with the given format and arguments. +func (l *Logger) Warn(format string, args ...any) { + wrap(l.context(), l, slog.LevelWarn, format, args...) +} + // Warnf logs at LevelWarn with the given format and arguments. func (l *Logger) Warnf(format string, args ...any) { wrapf(l.context(), l, slog.LevelWarn, format, args...) @@ -92,6 +98,11 @@ func (l *Logger) WarnContextf(ctx context.Context, format string, args ...any) { wrapf(ctx, l, slog.LevelWarn, format, args...) } +// Error logs at LevelError with the given format and arguments. +func (l *Logger) Error(format string, args ...any) { + wrap(l.context(), l, slog.LevelError, format, args...) +} + // Errorf logs at LevelError with the given format and arguments. func (l *Logger) Errorf(format string, args ...any) { wrapf(l.context(), l, slog.LevelError, format, args...) @@ -102,6 +113,11 @@ func (l *Logger) ErrorContextf(ctx context.Context, format string, args ...any) wrapf(ctx, l, slog.LevelError, format, args...) } +// Debug logs at LevelDebug with the given format and arguments. +func (l *Logger) Debug(format string, args ...any) { + wrap(l.context(), l, slog.LevelDebug, format, args...) +} + // Debugf logs at LevelDebug with the given format and arguments. func (l *Logger) Debugf(format string, args ...any) { wrapf(l.context(), l, slog.LevelDebug, format, args...) diff --git a/logger_test.go b/logger_test.go index ec209ab..537e461 100644 --- a/logger_test.go +++ b/logger_test.go @@ -187,3 +187,59 @@ func TestDefaultHandler(t *testing.T) { } }) } + +func TestContext(t *testing.T) { + old := slog.Default() + t.Cleanup(func() { + slog.SetDefault(old) + }) + + b := new(bytes.Buffer) + slog.SetDefault(slog.New(slog.NewJSONHandler(b, testopts))) + + msg := "hello world" + want := map[string]any{ + "level": "INFO", + "msg": msg, + "a": "b", + } + + ctx := WithValues(context.Background(), "a", "b") + // These should all give the same output and be functionally equivalent. + for _, tc := range []struct { + name string + fn func() + }{ + { + name: "clog.InfoContext", + fn: func() { + InfoContext(ctx, msg) + }, + }, + { + name: "clog.FromContext.Info", + fn: func() { + FromContext(ctx).Info(msg) + }, + }, + { + name: "clog.FromContext.InfoContext", + fn: func() { + FromContext(ctx).InfoContext(ctx, msg) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + b.Reset() + tc.fn() + + var got map[string]any + if err := json.Unmarshal(b.Bytes(), &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(want, got) { + t.Errorf("want %v, got %v", want, got) + } + }) + } +}