Skip to content

Commit 15d8fad

Browse files
committed
LogObject: optional interface for logging objects differently
The intention is to use this when the output is structured (like JSON) when the original type would be logged as string. It also has some other use cases. This approach was chosen instead of a full marshaler API as in zapcore.ObjectMarshaler because the implementation is simpler. The overhead for large types is expected to be higher, but it is not certain yet whether this is relevant in practice. If it is, then a marshaler API can still be added later.
1 parent 59f2313 commit 15d8fad

File tree

4 files changed

+138
-0
lines changed

4 files changed

+138
-0
lines changed

example_marshaler_secret_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2021 The logr Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package logr_test
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
)
22+
23+
// ComplexObjectRef contains more fields than it wants to get logged.
24+
type ComplexObjectRef struct {
25+
Name string
26+
Namespace string
27+
Secret string
28+
}
29+
30+
func (ref ComplexObjectRef) MarshalLog() interface{} {
31+
return struct {
32+
Name, Namespace string
33+
}{
34+
Name: ref.Name,
35+
Namespace: ref.Namespace,
36+
}
37+
}
38+
39+
var _ logr.Marshaler = ComplexObjectRef{}
40+
41+
func ExampleMarshaler_secret() {
42+
l := NewStdoutLogger()
43+
secret := ComplexObjectRef{Namespace: "kube-system", Name: "some-secret", Secret: "do-not-log-me"}
44+
l.Info("simplified", "secret", secret)
45+
// Output:
46+
// "level"=0 "msg"="simplified" "secret"={"Name":"some-secret","Namespace":"kube-system"}
47+
}

example_marshaler_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2021 The logr Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package logr_test
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/go-logr/logr"
23+
"github.com/go-logr/logr/funcr"
24+
)
25+
26+
// NewStdoutLogger returns a logr.Logger that prints to stdout.
27+
func NewStdoutLogger() logr.Logger {
28+
return funcr.New(func(prefix, args string) {
29+
if prefix != "" {
30+
_ = fmt.Sprintf("%s: %s\n", prefix, args)
31+
} else {
32+
fmt.Println(args)
33+
}
34+
}, funcr.Options{})
35+
}
36+
37+
// ObjectRef references a Kubernetes object
38+
type ObjectRef struct {
39+
Name string `json:"name"`
40+
Namespace string `json:"namespace,omitempty"`
41+
}
42+
43+
func (ref ObjectRef) String() string {
44+
if ref.Namespace != "" {
45+
return ref.Namespace + "/" + ref.Name
46+
}
47+
return ref.Name
48+
}
49+
50+
func (ref ObjectRef) MarshalLog() interface{} {
51+
// We implement fmt.Stringer for non-structured logging, but we want the
52+
// raw struct when using structured logs. Some logr implementations call
53+
// String if it is present, so we want to convert this struct to something
54+
// that doesn't have that method.
55+
type forLog ObjectRef // methods do not survive type definitions
56+
return forLog(ref)
57+
}
58+
59+
var _ logr.Marshaler = ObjectRef{}
60+
61+
func ExampleMarshaler() {
62+
l := NewStdoutLogger()
63+
pod := ObjectRef{Namespace: "kube-system", Name: "some-pod"}
64+
l.Info("as string", "pod", pod.String())
65+
l.Info("as struct", "pod", pod)
66+
// Output:
67+
// "level"=0 "msg"="as string" "pod"="kube-system/some-pod"
68+
// "level"=0 "msg"="as struct" "pod"={"name":"some-pod","namespace":"kube-system"}
69+
}

funcr/funcr.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ const (
170170
func prettyWithFlags(value interface{}, flags uint32) string {
171171
// Handling the most common types without reflect is a small perf win.
172172
switch v := value.(type) {
173+
case logr.Marshaler:
174+
// Replace the value with what the value wants to get logged.
175+
// That then gets handled below via reflection.
176+
value = v.MarshalLog()
173177
case bool:
174178
return strconv.FormatBool(v)
175179
case string:

logr.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,21 @@ type CallStackHelperLogSink interface {
477477
// call site information.
478478
GetCallStackHelper() func()
479479
}
480+
481+
// Marshaler is an optional interface that logged values may choose to
482+
// implement. Loggers with structured output, such as JSON, should
483+
// log the object return by the MarshalLog method instead of the
484+
// original value.
485+
type Marshaler interface {
486+
// MarshalLog can be used to:
487+
// - ensure that structs are not logged as strings when the original
488+
// value has a String method: return a different type without a
489+
// String method
490+
// - select which fields of a complex type should get logged:
491+
// return a simpler struct with fewer fields
492+
// - log unexported fields: return a different struct
493+
// with exported fields
494+
//
495+
// It may return any value of any type.
496+
MarshalLog() interface{}
497+
}

0 commit comments

Comments
 (0)