diff --git a/examples/benchmarks/benchmarks_test.go b/examples/benchmarks/benchmarks_test.go index eb8d5ae7..3105abaf 100644 --- a/examples/benchmarks/benchmarks_test.go +++ b/examples/benchmarks/benchmarks_test.go @@ -75,7 +75,7 @@ func BenchmarkOutput(b *testing.B) { tests = append(tests, testcase{ name: fmt.Sprintf("objects/%d", length), generate: func() interface{} { - return klog.KObjs(arg) + return klog.KObjSlice(arg) }, }) } diff --git a/k8s_references.go b/k8s_references.go index db58f8ba..2c218f69 100644 --- a/k8s_references.go +++ b/k8s_references.go @@ -77,6 +77,8 @@ func KRef(namespace, name string) ObjectRef { } // KObjs returns slice of ObjectRef from an slice of ObjectMeta +// +// DEPRECATED: Use KObjSlice instead, it has better performance. func KObjs(arg interface{}) []ObjectRef { s := reflect.ValueOf(arg) if s.Kind() != reflect.Slice { @@ -92,3 +94,65 @@ func KObjs(arg interface{}) []ObjectRef { } return objectRefs } + +// KObjSlice takes a slice of objects that implement the KMetadata interface +// and returns an object that gets logged as a slice of ObjectRef values or a +// string containing those values, depending on whether the logger prefers text +// output or structured output. +// +// An error string is logged when KObjSlice is not passed a suitable slice. +// +// Processing of the argument is delayed until the value actually gets logged, +// in contrast to KObjs where that overhead is incurred regardless of whether +// the result is needed. +func KObjSlice(arg interface{}) interface{} { + return kobjSlice{arg: arg} +} + +type kobjSlice struct { + arg interface{} +} + +var _ fmt.Stringer = kobjSlice{} +var _ logr.Marshaler = kobjSlice{} + +func (ks kobjSlice) String() string { + objectRefs, err := ks.process() + if err != nil { + return err.Error() + } + return fmt.Sprintf("%v", objectRefs) +} + +func (ks kobjSlice) MarshalLog() interface{} { + objectRefs, err := ks.process() + if err != nil { + return err.Error() + } + return objectRefs +} + +func (ks kobjSlice) process() ([]interface{}, error) { + s := reflect.ValueOf(ks.arg) + switch s.Kind() { + case reflect.Invalid: + // nil parameter, print as nil. + return nil, nil + case reflect.Slice: + // Okay, handle below. + default: + return nil, fmt.Errorf("", ks.arg) + } + objectRefs := make([]interface{}, 0, s.Len()) + for i := 0; i < s.Len(); i++ { + item := s.Index(i).Interface() + if item == nil { + objectRefs = append(objectRefs, nil) + } else if v, ok := item.(KMetadata); ok { + objectRefs = append(objectRefs, KObj(v)) + } else { + return nil, fmt.Errorf("", item) + } + } + return objectRefs, nil +} diff --git a/test/output.go b/test/output.go index bec12018..5325fed7 100644 --- a/test/output.go +++ b/test/output.go @@ -268,6 +268,47 @@ I output.go:] "test" firstKey=1 secondKey=3 &kmeta{Name: "pod-2", Namespace: "kube-system"}, })}, expectedOutput: `I output.go:] "test" pods=[kube-system/pod-1 kube-system/pod-2] +`, + }, + "KObjSlice okay": { + text: "test", + values: []interface{}{"pods", + klog.KObjSlice([]interface{}{ + &kmeta{Name: "pod-1", Namespace: "kube-system"}, + &kmeta{Name: "pod-2", Namespace: "kube-system"}, + })}, + expectedOutput: `I output.go:] "test" pods="[kube-system/pod-1 kube-system/pod-2]" +`, + }, + "KObjSlice nil arg": { + text: "test", + values: []interface{}{"pods", + klog.KObjSlice(nil)}, + expectedOutput: `I output.go:] "test" pods="[]" +`, + }, + "KObjSlice int arg": { + text: "test", + values: []interface{}{"pods", + klog.KObjSlice(1)}, + expectedOutput: `I output.go:] "test" pods="" +`, + }, + "KObjSlice nil entry": { + text: "test", + values: []interface{}{"pods", + klog.KObjSlice([]interface{}{ + &kmeta{Name: "pod-1", Namespace: "kube-system"}, + nil, + })}, + expectedOutput: `I output.go:] "test" pods="[kube-system/pod-1 ]" +`, + }, + "KObjSlice ints": { + text: "test", + values: []interface{}{"ints", + klog.KObjSlice([]int{1, 2, 3})}, + expectedOutput: `I output.go:] "test" ints="" `, }, "regular error types as value": { diff --git a/test/zapr.go b/test/zapr.go index 20b118aa..8bf1af56 100644 --- a/test/zapr.go +++ b/test/zapr.go @@ -67,6 +67,26 @@ func ZaprOutputMappingDirect() map[string]string { `I output.go:] "test" pods=[kube-system/pod-1 kube-system/pod-2] `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},{"name":"pod-2","namespace":"kube-system"}]} +`, + + `I output.go:] "test" pods="[kube-system/pod-1 kube-system/pod-2]" +`: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},{"name":"pod-2","namespace":"kube-system"}]} +`, + + `I output.go:] "test" pods="[]" +`: `{"caller":"test/output.go:","msg":"test","v":0,"pods":null} +`, + + `I output.go:] "test" pods="" +`: `{"caller":"test/output.go:","msg":"test","v":0,"pods":""} +`, + + `I output.go:] "test" ints="" +`: `{"caller":"test/output.go:","msg":"test","v":0,"ints":""} +`, + + `I output.go:] "test" pods="[kube-system/pod-1 ]" +`: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},null]} `, `I output.go:] "test" akey="avalue"