diff --git a/context.go b/context.go index 6c650a7..7bb07cb 100644 --- a/context.go +++ b/context.go @@ -16,10 +16,20 @@ func WithValue(parent context.Context, key string, val any) context.Context { panic("cannot create context from nil parent") } if v, ok := parent.Value(fields).(*sync.Map); ok { - v.Store(key, val) - return context.WithValue(parent, fields, v) + mapCopy := copySyncMap(v) + mapCopy.Store(key, val) + return context.WithValue(parent, fields, mapCopy) } v := &sync.Map{} v.Store(key, val) return context.WithValue(parent, fields, v) } + +func copySyncMap(m *sync.Map) *sync.Map { + var cp sync.Map + m.Range(func(k, v interface{}) bool { + cp.Store(k, v) + return true + }) + return &cp +} diff --git a/handler_test.go b/handler_test.go index 9a4efc9..9edc07c 100644 --- a/handler_test.go +++ b/handler_test.go @@ -2,9 +2,11 @@ package slogcontext import ( "context" + "encoding/json" "io" "log/slog" "os" + "strings" "testing" ) @@ -25,6 +27,22 @@ func TestHandler(t *testing.T) { logger.ErrorContext(ctx, "this is an error") } +func TestHandler_WithValue_ShouldNotAffectParent(t *testing.T) { + stringBuilder := strings.Builder{} + handler := NewHandler(slog.NewJSONHandler(&stringBuilder, nil)) + logger := slog.New(handler) + ctx := WithValue(context.Background(), "k1", "v1") + WithValue(ctx, "k2", "v2") + + logger.InfoContext(ctx, "this is an log") + + logMessage := jsonToMap(t, stringBuilder.String()) + _, ok := logMessage["k2"] + if ok { + t.Errorf("Log message shouldn't contain key added to different context") + } +} + func TestHandlerConcurrent(t *testing.T) { ctx := WithValue(context.Background(), "number", 12) ctx = WithValue(ctx, "string", "data") @@ -60,3 +78,12 @@ func BenchmarkHandler(b *testing.B) { logger.ErrorContext(ctx, "this is an error") } } + +func jsonToMap(t *testing.T, jsonStr string) map[string]any { + result := make(map[string]any) + err := json.Unmarshal([]byte(jsonStr), &result) + if err != nil { + t.Errorf("Failed serializing log message into a map: %v", err) + } + return result +}