diff --git a/examples/benchmarks/benchmarks_test.go b/examples/benchmarks/benchmarks_test.go index eb8d5ae71..3105abafe 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 db58f8baa..02ca78f18 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,57 @@ 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() ([]ObjectRef, error) { + s := reflect.ValueOf(ks.arg) + if s.Kind() != reflect.Slice { + return nil, fmt.Errorf("", ks.arg) + } + objectRefs := make([]ObjectRef, 0, s.Len()) + for i := 0; i < s.Len(); i++ { + item := s.Index(i).Interface() + 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 bec12018d..c735a4f8a 100644 --- a/test/output.go +++ b/test/output.go @@ -268,6 +268,40 @@ 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 nil entry": { + text: "test", + values: []interface{}{"pods", + klog.KObjSlice([]interface{}{ + &kmeta{Name: "pod-1", Namespace: "kube-system"}, + nil, + })}, + expectedOutput: `I output.go:] "test" pods=">" +`, + }, + "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 20b118aad..d137e7b6c 100644 --- a/test/zapr.go +++ b/test/zapr.go @@ -67,6 +67,22 @@ 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":">"} +`, + + `I output.go:] "test" ints="" +`: `{"caller":"test/output.go:","msg":"test","v":0,"ints":""} +`, + + `I output.go:] "test" pods=">" +`: `{"caller":"test/output.go:","msg":"test","v":0,"pods":">"} `, `I output.go:] "test" akey="avalue"