From dad393a306a9be3d822a7c51fc6763c296a3a88b Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 8 Feb 2023 08:32:14 -0500 Subject: [PATCH] tflog+tfsdklog: Prevent slice/map leaks when setting LoggerOpts (#132) Reference: https://github.com/hashicorp/terraform-plugin-log/issues/126 Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 Since the `LoggerOpts` struct contains slice and map fields, it is important to ensure any modifications occur on copies of those slices and maps, otherwise the memory reference can wind up being shared. Consumers should always be able to create a new `context.Context` without worrying about shared data. This change introduces a `Copy()` method for `LoggerOpts` and implements it for option modifier functions which adjust a map or slice. --- .../unreleased/BUG FIXES-20230207-165520.yaml | 6 + internal/logging/options.go | 41 ++ internal/logging/options_test.go | 112 +++++ tflog/provider.go | 36 +- tflog/provider_test.go | 391 +++++++++++++++++ tflog/subsystem.go | 36 +- tflog/subsystem_test.go | 400 ++++++++++++++++++ tfsdklog/sdk.go | 36 +- tfsdklog/sdk_test.go | 391 +++++++++++++++++ tfsdklog/subsystem.go | 36 +- tfsdklog/subsystem_test.go | 400 ++++++++++++++++++ 11 files changed, 1849 insertions(+), 36 deletions(-) create mode 100644 .changes/unreleased/BUG FIXES-20230207-165520.yaml create mode 100644 internal/logging/options_test.go diff --git a/.changes/unreleased/BUG FIXES-20230207-165520.yaml b/.changes/unreleased/BUG FIXES-20230207-165520.yaml new file mode 100644 index 0000000..7958bc8 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20230207-165520.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'tflog+tflogsdk: Prevented data race conditions when using SetField and other + option functions' +time: 2023-02-07T16:55:20.433603-05:00 +custom: + Issue: "132" diff --git a/internal/logging/options.go b/internal/logging/options.go index 8040699..583dc2c 100644 --- a/internal/logging/options.go +++ b/internal/logging/options.go @@ -157,6 +157,47 @@ type LoggerOpts struct { MaskMessageStrings []string } +// Copy creates a duplicate LoggerOpts. This should be used to ensure +// safe LoggerOpts modification when the LoggerOpts could be saved into a +// new context.Context. +func (o LoggerOpts) Copy() LoggerOpts { + result := LoggerOpts{ + AdditionalLocationOffset: o.AdditionalLocationOffset, + Fields: make(map[string]any, len(o.Fields)), + IncludeLocation: o.IncludeLocation, + IncludeRootFields: o.IncludeRootFields, + IncludeTime: o.IncludeTime, + Level: o.Level, + MaskAllFieldValuesRegexes: make([]*regexp.Regexp, len(o.MaskAllFieldValuesRegexes)), + MaskAllFieldValuesStrings: make([]string, len(o.MaskAllFieldValuesStrings)), + MaskFieldValuesWithFieldKeys: make([]string, len(o.MaskFieldValuesWithFieldKeys)), + MaskMessageRegexes: make([]*regexp.Regexp, len(o.MaskMessageRegexes)), + MaskMessageStrings: make([]string, len(o.MaskMessageStrings)), + Name: o.Name, + OmitLogWithFieldKeys: make([]string, len(o.OmitLogWithFieldKeys)), + OmitLogWithMessageRegexes: make([]*regexp.Regexp, len(o.OmitLogWithMessageRegexes)), + OmitLogWithMessageStrings: make([]string, len(o.OmitLogWithMessageStrings)), + Output: o.Output, + } + + // Copy all slice/map contents to prevent leaking memory references + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + for key, value := range o.Fields { + result.Fields[key] = value + } + + copy(result.MaskAllFieldValuesRegexes, o.MaskAllFieldValuesRegexes) + copy(result.MaskAllFieldValuesStrings, o.MaskAllFieldValuesStrings) + copy(result.MaskFieldValuesWithFieldKeys, o.MaskFieldValuesWithFieldKeys) + copy(result.MaskMessageRegexes, o.MaskMessageRegexes) + copy(result.MaskMessageStrings, o.MaskMessageStrings) + copy(result.OmitLogWithFieldKeys, o.OmitLogWithFieldKeys) + copy(result.OmitLogWithMessageRegexes, o.OmitLogWithMessageRegexes) + copy(result.OmitLogWithMessageStrings, o.OmitLogWithMessageStrings) + + return result +} + // ApplyLoggerOpts generates a LoggerOpts out of a list of Option // implementations. By default, AdditionalLocationOffset is 1, IncludeLocation // is true, IncludeTime is true, and Output is os.Stderr. diff --git a/internal/logging/options_test.go b/internal/logging/options_test.go new file mode 100644 index 0000000..b30d559 --- /dev/null +++ b/internal/logging/options_test.go @@ -0,0 +1,112 @@ +package logging_test + +import ( + "os" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-plugin-log/internal/logging" +) + +func TestLoggerOptsCopy(t *testing.T) { + t.Parallel() + + regex1 := regexp.MustCompile("regex1") + regex2 := regexp.MustCompile("regex2") + + // Populate all fields. + originalLoggerOpts := logging.LoggerOpts{ + AdditionalLocationOffset: 1, + Fields: map[string]any{"key1": "value1"}, + IncludeLocation: true, + IncludeRootFields: true, + IncludeTime: true, + Level: hclog.Error, + MaskAllFieldValuesRegexes: []*regexp.Regexp{regex1}, + MaskAllFieldValuesStrings: []string{"string1"}, + MaskFieldValuesWithFieldKeys: []string{"string1"}, + MaskMessageRegexes: []*regexp.Regexp{regex1}, + MaskMessageStrings: []string{"string1"}, + Name: "name1", + OmitLogWithFieldKeys: []string{"string1"}, + OmitLogWithMessageRegexes: []*regexp.Regexp{regex1}, + OmitLogWithMessageStrings: []string{"string1"}, + Output: os.Stdout, + } + + // Expected LoggerOpts should exactly match original. + expectedLoggerOpts := logging.LoggerOpts{ + AdditionalLocationOffset: 1, + Fields: map[string]any{"key1": "value1"}, + IncludeLocation: true, + IncludeRootFields: true, + IncludeTime: true, + Level: hclog.Error, + MaskAllFieldValuesRegexes: []*regexp.Regexp{regex1}, + MaskAllFieldValuesStrings: []string{"string1"}, + MaskFieldValuesWithFieldKeys: []string{"string1"}, + MaskMessageRegexes: []*regexp.Regexp{regex1}, + MaskMessageStrings: []string{"string1"}, + Name: "name1", + OmitLogWithFieldKeys: []string{"string1"}, + OmitLogWithMessageRegexes: []*regexp.Regexp{regex1}, + OmitLogWithMessageStrings: []string{"string1"}, + Output: os.Stdout, + } + + // Create a copy before modifying the original LoggerOpts. This will be + // checked against the expected LoggerOpts after modifications. + copiedLoggerOpts := originalLoggerOpts.Copy() + + // Ensure modifications of original does not effect copy. + originalLoggerOpts.AdditionalLocationOffset = 2 + originalLoggerOpts.Fields["key2"] = "value2" + originalLoggerOpts.IncludeLocation = false + originalLoggerOpts.IncludeRootFields = false + originalLoggerOpts.IncludeTime = false + originalLoggerOpts.Level = hclog.Debug + originalLoggerOpts.MaskAllFieldValuesRegexes = append(originalLoggerOpts.MaskAllFieldValuesRegexes, regex2) + originalLoggerOpts.MaskAllFieldValuesStrings = append(originalLoggerOpts.MaskAllFieldValuesStrings, "string2") + originalLoggerOpts.MaskFieldValuesWithFieldKeys = append(originalLoggerOpts.MaskFieldValuesWithFieldKeys, "string2") + originalLoggerOpts.MaskMessageRegexes = append(originalLoggerOpts.MaskMessageRegexes, regex2) + originalLoggerOpts.MaskMessageStrings = append(originalLoggerOpts.MaskMessageStrings, "string2") + originalLoggerOpts.Name = "name2" + originalLoggerOpts.OmitLogWithFieldKeys = append(originalLoggerOpts.OmitLogWithFieldKeys, "string2") + originalLoggerOpts.OmitLogWithMessageRegexes = append(originalLoggerOpts.OmitLogWithMessageRegexes, regex2) + originalLoggerOpts.OmitLogWithMessageStrings = append(originalLoggerOpts.OmitLogWithMessageStrings, "string2") + originalLoggerOpts.Output = os.Stderr + + // Prevent go-cmp errors. + cmpOpts := []cmp.Option{ + cmp.Comparer(func(i, j *os.File) bool { + if i == nil { + return j == nil + } + + if j == nil { + return false + } + + // Simple comparison test is good enough for our purposes. + return i.Fd() == j.Fd() + }), + cmp.Comparer(func(i, j *regexp.Regexp) bool { + if i == nil { + return j == nil + } + + if j == nil { + return false + } + + // Simple comparison test is good enough for our purposes. + return i.String() == j.String() + }), + } + + if diff := cmp.Diff(copiedLoggerOpts, expectedLoggerOpts, cmpOpts...); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/tflog/provider.go b/tflog/provider.go index 80a4e78..c1a1572 100644 --- a/tflog/provider.go +++ b/tflog/provider.go @@ -15,7 +15,9 @@ import ( func SetField(ctx context.Context, key string, value interface{}) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithField(key, value)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithField(key, value)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -152,7 +154,9 @@ func Error(ctx context.Context, msg string, additionalFields ...map[string]inter func OmitLogWithFieldKeys(ctx context.Context, keys ...string) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -174,7 +178,9 @@ func OmitLogWithFieldKeys(ctx context.Context, keys ...string) context.Context { func OmitLogWithMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -195,7 +201,9 @@ func OmitLogWithMessageRegexes(ctx context.Context, expressions ...*regexp.Regex func OmitLogWithMessageStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -217,7 +225,9 @@ func OmitLogWithMessageStrings(ctx context.Context, matchingStrings ...string) c func MaskFieldValuesWithFieldKeys(ctx context.Context, keys ...string) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -241,7 +251,9 @@ func MaskFieldValuesWithFieldKeys(ctx context.Context, keys ...string) context.C func MaskAllFieldValuesRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -265,7 +277,9 @@ func MaskAllFieldValuesRegexes(ctx context.Context, expressions ...*regexp.Regex func MaskAllFieldValuesStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -287,7 +301,9 @@ func MaskAllFieldValuesStrings(ctx context.Context, matchingStrings ...string) c func MaskMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } @@ -309,7 +325,9 @@ func MaskMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) cont func MaskMessageStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetProviderRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderRootTFLoggerOpts(ctx, lOpts) } diff --git a/tflog/provider_test.go b/tflog/provider_test.go index d409dc0..56397be 100644 --- a/tflog/provider_test.go +++ b/tflog/provider_test.go @@ -98,6 +98,49 @@ func TestSetField(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSetField_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.SetField(originalCtx, "key1", "value1") + + newCtx := tflog.SetField(originalCtx, "key2", "value2") + + tflog.Trace(originalCtx, "original logger") + tflog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider", + "key1": "value1", + // should not contain key2 field + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider", + "key1": "value1", + "key2": "value2", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestTrace(t *testing.T) { t.Parallel() @@ -651,6 +694,43 @@ func TestOmitLogWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.OmitLogWithFieldKeys(originalCtx, "key1") + + newCtx := tflog.OmitLogWithFieldKeys(originalCtx, "key2") + + tflog.Trace(originalCtx, "original logger", map[string]any{"key2": "value2"}) + tflog.Trace(newCtx, "new logger", map[string]any{"key1": "value1"}) + tflog.Trace(newCtx, "new logger", map[string]any{"key2": "value2"}) + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider", + "key2": "value2", + }, + // should omit new logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestOmitLogWithMessageRegexes(t *testing.T) { t.Parallel() @@ -737,6 +817,43 @@ func TestOmitLogWithMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.OmitLogWithMessageRegexes(originalCtx, regexp.MustCompile("original")) + + newCtx := tflog.OmitLogWithMessageRegexes(originalCtx, regexp.MustCompile("new")) + + tflog.Trace(originalCtx, "original should not be preserved") + tflog.Trace(originalCtx, "new should be preserved") + tflog.Trace(newCtx, "new should not be preserved") + tflog.Trace(newCtx, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestOmitLogWithMessageStrings(t *testing.T) { t.Parallel() @@ -823,6 +940,43 @@ func TestOmitLogWithMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.OmitLogWithMessageStrings(originalCtx, "original") + + newCtx := tflog.OmitLogWithMessageStrings(originalCtx, "new") + + tflog.Trace(originalCtx, "original should not be preserved") + tflog.Trace(originalCtx, "new should be preserved") + tflog.Trace(newCtx, "new should not be preserved") + tflog.Trace(newCtx, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskFieldValuesWithFieldKeys(t *testing.T) { t.Parallel() @@ -917,6 +1071,51 @@ func TestMaskFieldValuesWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskFieldValuesWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.SetField(originalCtx, "key1", "value1") + originalCtx = tflog.SetField(originalCtx, "key2", "value2") + originalCtx = tflog.MaskFieldValuesWithFieldKeys(originalCtx, "key1") + + newCtx := tflog.MaskFieldValuesWithFieldKeys(originalCtx, "key2") + + tflog.Trace(originalCtx, "original logger") + tflog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskAllFieldValuesRegexes(t *testing.T) { t.Parallel() @@ -1011,6 +1210,51 @@ func TestMaskAllFieldValuesRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskAllFieldValuesRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.SetField(originalCtx, "key1", "value1") + originalCtx = tflog.SetField(originalCtx, "key2", "value2") + originalCtx = tflog.MaskAllFieldValuesRegexes(originalCtx, regexp.MustCompile("value1")) + + newCtx := tflog.MaskAllFieldValuesRegexes(originalCtx, regexp.MustCompile("value2")) + + tflog.Trace(originalCtx, "original logger") + tflog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskAllFieldValuesStrings(t *testing.T) { t.Parallel() @@ -1105,6 +1349,51 @@ func TestMaskAllFieldValuesStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskAllFieldValuesStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.SetField(originalCtx, "key1", "value1") + originalCtx = tflog.SetField(originalCtx, "key2", "value2") + originalCtx = tflog.MaskAllFieldValuesStrings(originalCtx, "value1") + + newCtx := tflog.MaskAllFieldValuesStrings(originalCtx, "value2") + + tflog.Trace(originalCtx, "original logger") + tflog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskMessageRegexes(t *testing.T) { t.Parallel() @@ -1199,6 +1488,57 @@ func TestMaskMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.MaskMessageRegexes(originalCtx, regexp.MustCompile("original")) + + newCtx := tflog.MaskMessageRegexes(originalCtx, regexp.MustCompile("new")) + + tflog.Trace(originalCtx, "original should be masked") + tflog.Trace(originalCtx, "new should be preserved") + tflog.Debug(newCtx, "new should be masked") + tflog.Debug(newCtx, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "provider", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "provider", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskMessageStrings(t *testing.T) { t.Parallel() @@ -1293,6 +1633,57 @@ func TestMaskMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.MaskMessageStrings(originalCtx, "original") + + newCtx := tflog.MaskMessageStrings(originalCtx, "new") + + tflog.Trace(originalCtx, "original should be masked") + tflog.Trace(originalCtx, "new should be preserved") + tflog.Debug(newCtx, "new should be masked") + tflog.Debug(newCtx, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "provider", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "provider", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskLogRegexes(t *testing.T) { t.Parallel() diff --git a/tflog/subsystem.go b/tflog/subsystem.go index 6217041..1f66e75 100644 --- a/tflog/subsystem.go +++ b/tflog/subsystem.go @@ -84,7 +84,9 @@ func NewSubsystem(ctx context.Context, subsystem string, options ...logging.Opti func SubsystemSetField(ctx context.Context, subsystem, key string, value interface{}) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithField(key, value)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithField(key, value)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -231,7 +233,9 @@ func SubsystemError(ctx context.Context, subsystem, msg string, additionalFields func SubsystemOmitLogWithFieldKeys(ctx context.Context, subsystem string, keys ...string) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -253,7 +257,9 @@ func SubsystemOmitLogWithFieldKeys(ctx context.Context, subsystem string, keys . func SubsystemOmitLogWithMessageRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -274,7 +280,9 @@ func SubsystemOmitLogWithMessageRegexes(ctx context.Context, subsystem string, e func SubsystemOmitLogWithMessageStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -296,7 +304,9 @@ func SubsystemOmitLogWithMessageStrings(ctx context.Context, subsystem string, m func SubsystemMaskFieldValuesWithFieldKeys(ctx context.Context, subsystem string, keys ...string) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -320,7 +330,9 @@ func SubsystemMaskFieldValuesWithFieldKeys(ctx context.Context, subsystem string func SubsystemMaskAllFieldValuesRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -344,7 +356,9 @@ func SubsystemMaskAllFieldValuesRegexes(ctx context.Context, subsystem string, e func SubsystemMaskAllFieldValuesStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -366,7 +380,9 @@ func SubsystemMaskAllFieldValuesStrings(ctx context.Context, subsystem string, m func SubsystemMaskMessageRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -388,7 +404,9 @@ func SubsystemMaskMessageRegexes(ctx context.Context, subsystem string, expressi func SubsystemMaskMessageStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetProviderSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetProviderSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } diff --git a/tflog/subsystem_test.go b/tflog/subsystem_test.go index e7fc8b0..35136ad 100644 --- a/tflog/subsystem_test.go +++ b/tflog/subsystem_test.go @@ -104,6 +104,50 @@ func TestSubsystemSetField(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemSetField_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + + newCtx := tflog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider.test_subsystem", + "key1": "value1", + // should not contain key2 field + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider.test_subsystem", + "key1": "value1", + "key2": "value2", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemTrace(t *testing.T) { t.Parallel() @@ -661,6 +705,44 @@ func TestSubsystemOmitLogWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemOmitLogWithFieldKeys(originalCtx, testSubsystem, "key1") + + newCtx := tflog.SubsystemOmitLogWithFieldKeys(originalCtx, testSubsystem, "key2") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original logger", map[string]any{"key2": "value2"}) + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger", map[string]any{"key1": "value1"}) + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger", map[string]any{"key2": "value2"}) + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider.test_subsystem", + "key2": "value2", + }, + // should omit new logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemOmitLogWithMessageRegexes(t *testing.T) { t.Parallel() @@ -748,6 +830,44 @@ func TestSubsystemOmitLogWithMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemOmitLogWithMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("original")) + + newCtx := tflog.SubsystemOmitLogWithMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("new")) + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original should not be preserved") + tflog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "new should not be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider.test_subsystem", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemOmitLogWithMessageStrings(t *testing.T) { t.Parallel() @@ -835,6 +955,44 @@ func TestSubsystemOmitLogWithMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemOmitLogWithMessageStrings(originalCtx, testSubsystem, "original") + + newCtx := tflog.SubsystemOmitLogWithMessageStrings(originalCtx, testSubsystem, "new") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original should not be preserved") + tflog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "new should not be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider.test_subsystem", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskFieldValuesWithFieldKeys(t *testing.T) { t.Parallel() @@ -930,6 +1088,52 @@ func TestSubsystemMaskFieldValuesWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskFieldValuesWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tflog.SubsystemMaskFieldValuesWithFieldKeys(originalCtx, testSubsystem, "key1") + + newCtx := tflog.SubsystemMaskFieldValuesWithFieldKeys(originalCtx, testSubsystem, "key2") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskAllFieldValuesRegexes(t *testing.T) { t.Parallel() @@ -1025,6 +1229,52 @@ func TestSubsystemMaskAllFieldValuesRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskAllFieldValuesRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tflog.SubsystemMaskAllFieldValuesRegexes(originalCtx, testSubsystem, regexp.MustCompile("value1")) + + newCtx := tflog.SubsystemMaskAllFieldValuesRegexes(originalCtx, testSubsystem, regexp.MustCompile("value2")) + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskAllFieldValuesStrings(t *testing.T) { t.Parallel() @@ -1120,6 +1370,52 @@ func TestSubsystemMaskAllFieldValuesStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskAllFieldValuesStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tflog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tflog.SubsystemMaskAllFieldValuesStrings(originalCtx, testSubsystem, "value1") + + newCtx := tflog.SubsystemMaskAllFieldValuesStrings(originalCtx, testSubsystem, "value2") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tflog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "provider.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskMessageRegexes(t *testing.T) { t.Parallel() @@ -1215,6 +1511,58 @@ func TestSubsystemMaskMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemMaskMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("original")) + + newCtx := tflog.SubsystemMaskMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("new")) + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original should be masked") + tflog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "new should be masked") + tflog.SubsystemTrace(newCtx, testSubsystem, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskMessageStrings(t *testing.T) { t.Parallel() @@ -1310,6 +1658,58 @@ func TestSubsystemMaskMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.ProviderRoot(originalCtx, &outputBuffer) + originalCtx = tflog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tflog.SubsystemMaskMessageStrings(originalCtx, testSubsystem, "original") + + newCtx := tflog.SubsystemMaskMessageStrings(originalCtx, testSubsystem, "new") + + tflog.SubsystemTrace(originalCtx, testSubsystem, "original should be masked") + tflog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tflog.SubsystemTrace(newCtx, testSubsystem, "new should be masked") + tflog.SubsystemTrace(newCtx, testSubsystem, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "provider.test_subsystem", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskLogRegexes(t *testing.T) { t.Parallel() diff --git a/tfsdklog/sdk.go b/tfsdklog/sdk.go index 902d19a..2891ddf 100644 --- a/tfsdklog/sdk.go +++ b/tfsdklog/sdk.go @@ -103,7 +103,9 @@ func NewRootProviderLogger(ctx context.Context, options ...logging.Option) conte func SetField(ctx context.Context, key string, value interface{}) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithField(key, value)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithField(key, value)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -235,7 +237,9 @@ func Error(ctx context.Context, msg string, additionalFields ...map[string]inter func OmitLogWithFieldKeys(ctx context.Context, keys ...string) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -257,7 +261,9 @@ func OmitLogWithFieldKeys(ctx context.Context, keys ...string) context.Context { func OmitLogWithMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -278,7 +284,9 @@ func OmitLogWithMessageRegexes(ctx context.Context, expressions ...*regexp.Regex func OmitLogWithMessageStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -300,7 +308,9 @@ func OmitLogWithMessageStrings(ctx context.Context, matchingStrings ...string) c func MaskFieldValuesWithFieldKeys(ctx context.Context, keys ...string) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -324,7 +334,9 @@ func MaskFieldValuesWithFieldKeys(ctx context.Context, keys ...string) context.C func MaskAllFieldValuesRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -348,7 +360,9 @@ func MaskAllFieldValuesRegexes(ctx context.Context, expressions ...*regexp.Regex func MaskAllFieldValuesStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -370,7 +384,9 @@ func MaskAllFieldValuesStrings(ctx context.Context, matchingStrings ...string) c func MaskMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } @@ -392,7 +408,9 @@ func MaskMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) cont func MaskMessageStrings(ctx context.Context, matchingStrings ...string) context.Context { lOpts := logging.GetSDKRootTFLoggerOpts(ctx) - lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKRootTFLoggerOpts(ctx, lOpts) } diff --git a/tfsdklog/sdk_test.go b/tfsdklog/sdk_test.go index 73642e0..5c0d1d4 100644 --- a/tfsdklog/sdk_test.go +++ b/tfsdklog/sdk_test.go @@ -98,6 +98,49 @@ func TestSetField(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSetField_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.SetField(originalCtx, "key1", "value1") + + newCtx := tfsdklog.SetField(originalCtx, "key2", "value2") + + tfsdklog.Trace(originalCtx, "original logger") + tfsdklog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk", + "key1": "value1", + // should not contain key2 field + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk", + "key1": "value1", + "key2": "value2", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestTrace(t *testing.T) { t.Parallel() @@ -651,6 +694,43 @@ func TestOmitLogWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.OmitLogWithFieldKeys(originalCtx, "key1") + + newCtx := tfsdklog.OmitLogWithFieldKeys(originalCtx, "key2") + + tfsdklog.Trace(originalCtx, "original logger", map[string]any{"key2": "value2"}) + tfsdklog.Trace(newCtx, "new logger", map[string]any{"key1": "value1"}) + tfsdklog.Trace(newCtx, "new logger", map[string]any{"key2": "value2"}) + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk", + "key2": "value2", + }, + // should omit new logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestOmitLogWithMessageRegexes(t *testing.T) { t.Parallel() @@ -737,6 +817,43 @@ func TestOmitLogWithMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.OmitLogWithMessageRegexes(originalCtx, regexp.MustCompile("original")) + + newCtx := tfsdklog.OmitLogWithMessageRegexes(originalCtx, regexp.MustCompile("new")) + + tfsdklog.Trace(originalCtx, "original should not be preserved") + tfsdklog.Trace(originalCtx, "new should be preserved") + tfsdklog.Trace(newCtx, "new should not be preserved") + tfsdklog.Trace(newCtx, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestOmitLogWithMessageStrings(t *testing.T) { t.Parallel() @@ -823,6 +940,43 @@ func TestOmitLogWithMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestOmitLogWithMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.OmitLogWithMessageStrings(originalCtx, "original") + + newCtx := tfsdklog.OmitLogWithMessageStrings(originalCtx, "new") + + tfsdklog.Trace(originalCtx, "original should not be preserved") + tfsdklog.Trace(originalCtx, "new should be preserved") + tfsdklog.Trace(newCtx, "new should not be preserved") + tfsdklog.Trace(newCtx, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskFieldValuesWithFieldKeys(t *testing.T) { t.Parallel() @@ -917,6 +1071,51 @@ func TestMaskFieldValuesWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskFieldValuesWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.SetField(originalCtx, "key1", "value1") + originalCtx = tfsdklog.SetField(originalCtx, "key2", "value2") + originalCtx = tfsdklog.MaskFieldValuesWithFieldKeys(originalCtx, "key1") + + newCtx := tfsdklog.MaskFieldValuesWithFieldKeys(originalCtx, "key2") + + tfsdklog.Trace(originalCtx, "original logger") + tfsdklog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskAllFieldValuesRegexes(t *testing.T) { t.Parallel() @@ -1011,6 +1210,51 @@ func TestMaskAllFieldValuesRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskAllFieldValuesRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.SetField(originalCtx, "key1", "value1") + originalCtx = tfsdklog.SetField(originalCtx, "key2", "value2") + originalCtx = tfsdklog.MaskAllFieldValuesRegexes(originalCtx, regexp.MustCompile("value1")) + + newCtx := tfsdklog.MaskAllFieldValuesRegexes(originalCtx, regexp.MustCompile("value2")) + + tfsdklog.Trace(originalCtx, "original logger") + tfsdklog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskAllFieldValuesStrings(t *testing.T) { t.Parallel() @@ -1105,6 +1349,51 @@ func TestMaskAllFieldValuesStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskAllFieldValuesStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.SetField(originalCtx, "key1", "value1") + originalCtx = tfsdklog.SetField(originalCtx, "key2", "value2") + originalCtx = tfsdklog.MaskAllFieldValuesStrings(originalCtx, "value1") + + newCtx := tfsdklog.MaskAllFieldValuesStrings(originalCtx, "value2") + + tfsdklog.Trace(originalCtx, "original logger") + tfsdklog.Trace(newCtx, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskMessageRegexes(t *testing.T) { t.Parallel() @@ -1199,6 +1488,57 @@ func TestMaskMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.MaskMessageRegexes(originalCtx, regexp.MustCompile("original")) + + newCtx := tfsdklog.MaskMessageRegexes(originalCtx, regexp.MustCompile("new")) + + tfsdklog.Trace(originalCtx, "original should be masked") + tfsdklog.Trace(originalCtx, "new should be preserved") + tfsdklog.Debug(newCtx, "new should be masked") + tfsdklog.Debug(newCtx, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "sdk", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "sdk", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskMessageStrings(t *testing.T) { t.Parallel() @@ -1293,6 +1633,57 @@ func TestMaskMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestMaskMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.MaskMessageStrings(originalCtx, "original") + + newCtx := tfsdklog.MaskMessageStrings(originalCtx, "new") + + tfsdklog.Trace(originalCtx, "original should be masked") + tfsdklog.Trace(originalCtx, "new should be preserved") + tfsdklog.Debug(newCtx, "new should be masked") + tfsdklog.Debug(newCtx, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "sdk", + }, + { + "@level": "debug", + "@message": "*** should be masked", + "@module": "sdk", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestMaskLogRegexes(t *testing.T) { t.Parallel() diff --git a/tfsdklog/subsystem.go b/tfsdklog/subsystem.go index 1a1b771..4d2a1a1 100644 --- a/tfsdklog/subsystem.go +++ b/tfsdklog/subsystem.go @@ -84,7 +84,9 @@ func NewSubsystem(ctx context.Context, subsystem string, options ...logging.Opti func SubsystemSetField(ctx context.Context, subsystem, key string, value interface{}) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithField(key, value)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithField(key, value)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -231,7 +233,9 @@ func SubsystemError(ctx context.Context, subsystem, msg string, additionalFields func SubsystemOmitLogWithFieldKeys(ctx context.Context, subsystem string, keys ...string) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -253,7 +257,9 @@ func SubsystemOmitLogWithFieldKeys(ctx context.Context, subsystem string, keys . func SubsystemOmitLogWithMessageRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -274,7 +280,9 @@ func SubsystemOmitLogWithMessageRegexes(ctx context.Context, subsystem string, e func SubsystemOmitLogWithMessageStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -296,7 +304,9 @@ func SubsystemOmitLogWithMessageStrings(ctx context.Context, subsystem string, m func SubsystemMaskFieldValuesWithFieldKeys(ctx context.Context, subsystem string, keys ...string) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -318,7 +328,9 @@ func SubsystemMaskFieldValuesWithFieldKeys(ctx context.Context, subsystem string func SubsystemMaskAllFieldValuesRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -340,7 +352,9 @@ func SubsystemMaskAllFieldValuesRegexes(ctx context.Context, subsystem string, e func SubsystemMaskAllFieldValuesStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -362,7 +376,9 @@ func SubsystemMaskAllFieldValuesStrings(ctx context.Context, subsystem string, m func SubsystemMaskMessageRegexes(ctx context.Context, subsystem string, expressions ...*regexp.Regexp) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } @@ -384,7 +400,9 @@ func SubsystemMaskMessageRegexes(ctx context.Context, subsystem string, expressi func SubsystemMaskMessageStrings(ctx context.Context, subsystem string, matchingStrings ...string) context.Context { lOpts := logging.GetSDKSubsystemTFLoggerOpts(ctx, subsystem) - lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts) + // Copy to prevent slice/map aliasing issues. + // Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 + lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts.Copy()) return logging.SetSDKSubsystemTFLoggerOpts(ctx, subsystem, lOpts) } diff --git a/tfsdklog/subsystem_test.go b/tfsdklog/subsystem_test.go index 6fed24b..7c7c77d 100644 --- a/tfsdklog/subsystem_test.go +++ b/tfsdklog/subsystem_test.go @@ -104,6 +104,50 @@ func TestSubsystemSetField(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemSetField_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + + newCtx := tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk.test_subsystem", + "key1": "value1", + // should not contain key2 field + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk.test_subsystem", + "key1": "value1", + "key2": "value2", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemTrace(t *testing.T) { t.Parallel() @@ -661,6 +705,44 @@ func TestSubsystemOmitLogWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemOmitLogWithFieldKeys(originalCtx, testSubsystem, "key1") + + newCtx := tfsdklog.SubsystemOmitLogWithFieldKeys(originalCtx, testSubsystem, "key2") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original logger", map[string]any{"key2": "value2"}) + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger", map[string]any{"key1": "value1"}) + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger", map[string]any{"key2": "value2"}) + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk.test_subsystem", + "key2": "value2", + }, + // should omit new logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemOmitLogWithMessageRegexes(t *testing.T) { t.Parallel() @@ -748,6 +830,44 @@ func TestSubsystemOmitLogWithMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemOmitLogWithMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("original")) + + newCtx := tfsdklog.SubsystemOmitLogWithMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("new")) + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original should not be preserved") + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new should not be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk.test_subsystem", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemOmitLogWithMessageStrings(t *testing.T) { t.Parallel() @@ -835,6 +955,44 @@ func TestSubsystemOmitLogWithMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemOmitLogWithMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemOmitLogWithMessageStrings(originalCtx, testSubsystem, "original") + + newCtx := tfsdklog.SubsystemOmitLogWithMessageStrings(originalCtx, testSubsystem, "new") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original should not be preserved") + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new should not be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "original should not be preserved") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk.test_subsystem", + }, + // should omit other logger entries + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskFieldValuesWithFieldKeys(t *testing.T) { t.Parallel() @@ -930,6 +1088,52 @@ func TestSubsystemMaskFieldValuesWithFieldKeys(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskFieldValuesWithFieldKeys_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tfsdklog.SubsystemMaskFieldValuesWithFieldKeys(originalCtx, testSubsystem, "key1") + + newCtx := tfsdklog.SubsystemMaskFieldValuesWithFieldKeys(originalCtx, testSubsystem, "key2") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskAllFieldValuesRegexes(t *testing.T) { t.Parallel() @@ -1025,6 +1229,52 @@ func TestSubsystemMaskAllFieldValuesRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskAllFieldValuesRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tfsdklog.SubsystemMaskAllFieldValuesRegexes(originalCtx, testSubsystem, regexp.MustCompile("value1")) + + newCtx := tfsdklog.SubsystemMaskAllFieldValuesRegexes(originalCtx, testSubsystem, regexp.MustCompile("value2")) + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskAllFieldValuesStrings(t *testing.T) { t.Parallel() @@ -1120,6 +1370,52 @@ func TestSubsystemMaskAllFieldValuesStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskAllFieldValuesStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key1", "value1") + originalCtx = tfsdklog.SubsystemSetField(originalCtx, testSubsystem, "key2", "value2") + originalCtx = tfsdklog.SubsystemMaskAllFieldValuesStrings(originalCtx, testSubsystem, "value1") + + newCtx := tfsdklog.SubsystemMaskAllFieldValuesStrings(originalCtx, testSubsystem, "value2") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original logger") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new logger") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "original logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "value2", + }, + { + "@level": "trace", + "@message": "new logger", + "@module": "sdk.test_subsystem", + "key1": "***", + "key2": "***", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskMessageRegexes(t *testing.T) { t.Parallel() @@ -1215,6 +1511,58 @@ func TestSubsystemMaskMessageRegexes(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskMessageRegexes_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemMaskMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("original")) + + newCtx := tfsdklog.SubsystemMaskMessageRegexes(originalCtx, testSubsystem, regexp.MustCompile("new")) + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original should be masked") + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new should be masked") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskMessageStrings(t *testing.T) { t.Parallel() @@ -1310,6 +1658,58 @@ func TestSubsystemMaskMessageStrings(t *testing.T) { } } +// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131 +func TestSubsystemMaskMessageStrings_NewContext(t *testing.T) { + t.Parallel() + + var outputBuffer bytes.Buffer + + originalCtx := context.Background() + originalCtx = loggertest.SDKRoot(originalCtx, &outputBuffer) + originalCtx = tfsdklog.NewSubsystem(originalCtx, testSubsystem) + originalCtx = tfsdklog.SubsystemMaskMessageStrings(originalCtx, testSubsystem, "original") + + newCtx := tfsdklog.SubsystemMaskMessageStrings(originalCtx, testSubsystem, "new") + + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "original should be masked") + tfsdklog.SubsystemTrace(originalCtx, testSubsystem, "new should be preserved") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "new should be masked") + tfsdklog.SubsystemTrace(newCtx, testSubsystem, "original should be masked") + + got, err := loggertest.MultilineJSONDecode(&outputBuffer) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedOutput := []map[string]any{ + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "new should be preserved", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + { + "@level": "trace", + "@message": "*** should be masked", + "@module": "sdk.test_subsystem", + }, + } + + if diff := cmp.Diff(expectedOutput, got); diff != "" { + t.Errorf("unexpected new logger output difference: %s", diff) + } +} + func TestSubsystemMaskLogRegexes(t *testing.T) { t.Parallel()