-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tflog+tfsdklog: Change final variadic function parameter in logging functions to ...map[string]interface{} #34
Changes from 4 commits
4562edc
933cd77
aa4a5f5
2e5c436
ae49fe5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
```release-note:breaking-change | ||
tflog: The `Trace()`, `Debug()`, `Info()`, `Warn()`, and `Error()` functions and `Subsystem` equivalents now use `...map[string]interface{}` as the final optional parameter, where the `string` is the structured logging key, rather than expecting matched `key interface{}, value interface{}` pairs. If multiple maps contain the same key, the value is shallow merged. | ||
``` | ||
|
||
```release-note:breaking-change | ||
tfsdklog: The `Trace()`, `Debug()`, `Info()`, `Warn()`, and `Error()` functions and `Subsystem` equivalents now use `...map[string]interface{}` as the final optional parameter, where the `string` is the structured logging key, rather than expecting matched `key interface{}, value interface{}` pairs. If multiple maps contain the same key, the value is shallow merged. | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package hclogutils | ||
|
||
// MapsToArgs will shallow merge field maps into the slice of key1, value1, | ||
// key2, value2, ... arguments expected by hc-log.Logger methods. | ||
func MapsToArgs(maps ...map[string]interface{}) []interface{} { | ||
switch len(maps) { | ||
case 0: | ||
return nil | ||
case 1: | ||
result := make([]interface{}, 0, len(maps[0])*2) | ||
|
||
for k, v := range maps[0] { | ||
result = append(result, k) | ||
result = append(result, v) | ||
} | ||
|
||
return result | ||
default: | ||
mergedMap := make(map[string]interface{}, 0) | ||
|
||
for _, m := range maps { | ||
for k, v := range m { | ||
mergedMap[k] = v | ||
} | ||
} | ||
|
||
result := make([]interface{}, 0, len(mergedMap)*2) | ||
|
||
for k, v := range mergedMap { | ||
result = append(result, k) | ||
result = append(result, v) | ||
} | ||
|
||
return result | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package hclogutils_test | ||
|
||
import ( | ||
"sort" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/hashicorp/terraform-plugin-log/internal/hclogutils" | ||
) | ||
|
||
func TestMapsToArgs(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := map[string]struct { | ||
maps []map[string]interface{} | ||
expectedArgs []interface{} | ||
}{ | ||
"nil": { | ||
maps: nil, | ||
expectedArgs: nil, | ||
}, | ||
"map-single": { | ||
maps: []map[string]interface{}{ | ||
{ | ||
"map1-key1": "map1-value1", | ||
"map1-key2": "map1-value2", | ||
"map1-key3": "map1-value3", | ||
}, | ||
}, | ||
expectedArgs: []interface{}{ | ||
"map1-key1", "map1-value1", | ||
"map1-key2", "map1-value2", | ||
"map1-key3", "map1-value3", | ||
}, | ||
}, | ||
"map-multiple-different-keys": { | ||
maps: []map[string]interface{}{ | ||
{ | ||
"map1-key1": "map1-value1", | ||
"map1-key2": "map1-value2", | ||
"map1-key3": "map1-value3", | ||
}, | ||
{ | ||
"map2-key1": "map2-value1", | ||
"map2-key2": "map2-value2", | ||
"map2-key3": "map2-value3", | ||
}, | ||
}, | ||
expectedArgs: []interface{}{ | ||
"map1-key1", "map1-value1", | ||
"map1-key2", "map1-value2", | ||
"map1-key3", "map1-value3", | ||
"map2-key1", "map2-value1", | ||
"map2-key2", "map2-value2", | ||
"map2-key3", "map2-value3", | ||
}, | ||
}, | ||
"map-multiple-mixed-keys": { | ||
maps: []map[string]interface{}{ | ||
{ | ||
"key1": "map1-value1", | ||
"key2": "map1-value2", | ||
"key3": "map1-value3", | ||
}, | ||
{ | ||
"key4": "map2-value4", | ||
"key1": "map2-value1", | ||
"key5": "map2-value5", | ||
}, | ||
}, | ||
expectedArgs: []interface{}{ | ||
"key1", "map2-value1", | ||
"key2", "map1-value2", | ||
"key3", "map1-value3", | ||
"key4", "map2-value4", | ||
"key5", "map2-value5", | ||
}, | ||
}, | ||
"map-multiple-overlapping-keys": { | ||
maps: []map[string]interface{}{ | ||
{ | ||
"key1": "map1-value1", | ||
"key2": "map1-value2", | ||
"key3": "map1-value3", | ||
}, | ||
{ | ||
"key1": "map2-value1", | ||
"key2": "map2-value2", | ||
"key3": "map2-value3", | ||
}, | ||
}, | ||
expectedArgs: []interface{}{ | ||
"key1", "map2-value1", | ||
"key2", "map2-value2", | ||
"key3", "map2-value3", | ||
}, | ||
}, | ||
"map-multiple-overlapping-keys-shallow": { | ||
maps: []map[string]interface{}{ | ||
{ | ||
"key1": map[string]interface{}{ | ||
"submap-key1": "map1-value1", | ||
"submap-key2": "map1-value2", | ||
"submap-key3": "map1-value3", | ||
}, | ||
"key2": "map1-value2", | ||
"key3": "map1-value3", | ||
}, | ||
{ | ||
"key1": map[string]interface{}{ | ||
"submap-key4": "map2-value4", | ||
"submap-key5": "map2-value5", | ||
"submap-key6": "map2-value6", | ||
}, | ||
"key2": "map2-value2", | ||
"key3": "map2-value3", | ||
}, | ||
}, | ||
expectedArgs: []interface{}{ | ||
"key1", map[string]interface{}{ | ||
"submap-key4": "map2-value4", | ||
"submap-key5": "map2-value5", | ||
"submap-key6": "map2-value6", | ||
}, | ||
"key2", "map2-value2", | ||
"key3", "map2-value3", | ||
}, | ||
}, | ||
} | ||
|
||
for name, testCase := range testCases { | ||
name, testCase := name, testCase | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
got := hclogutils.MapsToArgs(testCase.maps...) | ||
|
||
if len(got)%2 != 0 { | ||
t.Fatalf("expected even number of key-value fields, got: %v", got) | ||
} | ||
|
||
if got == nil && testCase.expectedArgs == nil { | ||
return // sortedGot will return []interface{}{} below, nil is what we want | ||
} | ||
|
||
// Map retrieval is indeterminate in Go, sort the result first. | ||
// This logic is only necessary in this testing as its automatically | ||
// handled in go-hclog. | ||
gotKeys := make([]string, 0, len(got)/2) | ||
gotValues := make(map[string]interface{}, len(got)/2) | ||
|
||
for i := 0; i < len(got); i += 2 { | ||
k, v := got[i].(string), got[i+1] | ||
gotKeys = append(gotKeys, k) | ||
gotValues[k] = v | ||
} | ||
|
||
sort.Strings(gotKeys) | ||
|
||
sortedGot := make([]interface{}, 0, len(got)) | ||
|
||
for _, k := range gotKeys { | ||
sortedGot = append(sortedGot, k) | ||
sortedGot = append(sortedGot, gotValues[k]) | ||
} | ||
|
||
if diff := cmp.Diff(sortedGot, testCase.expectedArgs); diff != "" { | ||
t.Errorf("unexpected difference: %s", diff) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,10 @@ func ExampleTrace() { | |
exampleCtx := getExampleContext() | ||
|
||
// non-example-setup code begins here | ||
Trace(exampleCtx, "hello, world", "foo", 123, "colors", []string{"red", "blue", "green"}) | ||
Trace(exampleCtx, "hello, world", map[string]interface{}{ | ||
"foo": 123, | ||
"colors": []string{"red", "blue", "green"}, | ||
}) | ||
Comment on lines
+50
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Funny enough, I created a similar construct in my last gig: it was Java Exception but the idea was the same. Exceptions happening deep in a call stack would carry extra context in the form of So, I'm a big fan of this refactoring you are doing. |
||
|
||
// Output: | ||
// {"@level":"trace","@message":"hello, world","@module":"provider","colors":["red","blue","green"],"foo":123} | ||
|
@@ -64,7 +67,10 @@ func ExampleDebug() { | |
exampleCtx := getExampleContext() | ||
|
||
// non-example-setup code begins here | ||
Debug(exampleCtx, "hello, world", "foo", 123, "colors", []string{"red", "blue", "green"}) | ||
Debug(exampleCtx, "hello, world", map[string]interface{}{ | ||
"foo": 123, | ||
"colors": []string{"red", "blue", "green"}, | ||
}) | ||
|
||
// Output: | ||
// {"@level":"debug","@message":"hello, world","@module":"provider","colors":["red","blue","green"],"foo":123} | ||
|
@@ -81,7 +87,10 @@ func ExampleInfo() { | |
exampleCtx := getExampleContext() | ||
|
||
// non-example-setup code begins here | ||
Info(exampleCtx, "hello, world", "foo", 123, "colors", []string{"red", "blue", "green"}) | ||
Info(exampleCtx, "hello, world", map[string]interface{}{ | ||
"foo": 123, | ||
"colors": []string{"red", "blue", "green"}, | ||
}) | ||
|
||
// Output: | ||
// {"@level":"info","@message":"hello, world","@module":"provider","colors":["red","blue","green"],"foo":123} | ||
|
@@ -98,7 +107,10 @@ func ExampleWarn() { | |
exampleCtx := getExampleContext() | ||
|
||
// non-example-setup code begins here | ||
Warn(exampleCtx, "hello, world", "foo", 123, "colors", []string{"red", "blue", "green"}) | ||
Warn(exampleCtx, "hello, world", map[string]interface{}{ | ||
"foo": 123, | ||
"colors": []string{"red", "blue", "green"}, | ||
}) | ||
|
||
// Output: | ||
// {"@level":"warn","@message":"hello, world","@module":"provider","colors":["red","blue","green"],"foo":123} | ||
|
@@ -115,7 +127,10 @@ func ExampleError() { | |
exampleCtx := getExampleContext() | ||
|
||
// non-example-setup code begins here | ||
Error(exampleCtx, "hello, world", "foo", 123, "colors", []string{"red", "blue", "green"}) | ||
Error(exampleCtx, "hello, world", map[string]interface{}{ | ||
"foo": 123, | ||
"colors": []string{"red", "blue", "green"}, | ||
}) | ||
|
||
// Output: | ||
// {"@level":"error","@message":"hello, world","@module":"provider","colors":["red","blue","green"],"foo":123} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct if I'm wrong: what you are really after here (to feed to the second call to
make()
is for the sum of the number of keys in each map. So the creation of the intermediarymergedMap
feels unnecessary memory allocation that will be thrown away when we get out of here.It is a nitpick, where probably allocating and deallocating a map ain't a bit deal, but I feel Id be remiss if I didn't suggest a cheaper approach:
result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intermediate map performs key deduplication, otherwise, yeah we would just loop through all the maps to append to a single slice. Refer also to the unit tests such as
map-multiple-mixed-keys
andmap-multiple-overlapping-keys
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see what you are doing.