@@ -19,33 +19,26 @@ limitations under the License.
19
19
package log
20
20
21
21
import (
22
+ "fmt"
22
23
"io"
23
- "log "
24
+ "os "
24
25
"time"
25
26
26
27
"github.com/go-logr/logr"
27
28
"github.com/go-logr/zapr"
28
29
"go.uber.org/zap"
30
+ "go.uber.org/zap/buffer"
29
31
"go.uber.org/zap/zapcore"
32
+ "k8s.io/apimachinery/pkg/api/meta"
33
+ "k8s.io/apimachinery/pkg/runtime"
30
34
)
31
35
32
36
// ZapLogger is a Logger implementation.
33
37
// If development is true, a Zap development config will be used
34
38
// (stacktraces on warnings, no sampling), otherwise a Zap production
35
39
// config will be used (stacktraces on errors, sampling).
36
40
func ZapLogger (development bool ) logr.Logger {
37
- var zapLog * zap.Logger
38
- var err error
39
- if development {
40
- zapLogCfg := zap .NewDevelopmentConfig ()
41
- zapLog , err = zapLogCfg .Build (zap .AddCallerSkip (1 ))
42
- } else {
43
- zapLogCfg := zap .NewProductionConfig ()
44
- zapLog , err = zapLogCfg .Build (zap .AddCallerSkip (1 ))
45
- }
46
- // who watches the watchmen?
47
- fatalIfErr (err , log .Fatalf )
48
- return zapr .NewLogger (zapLog )
41
+ return ZapLoggerTo (os .Stderr , development )
49
42
}
50
43
51
44
// ZapLoggerTo returns a new Logger implementation using Zap which logs
@@ -73,15 +66,87 @@ func ZapLoggerTo(destWriter io.Writer, development bool) logr.Logger {
73
66
}))
74
67
}
75
68
opts = append (opts , zap .AddCallerSkip (1 ), zap .ErrorOutput (sink ))
76
- log := zap .New (zapcore .NewCore (enc , sink , lvl ))
69
+ log := zap .New (zapcore .NewCore (& KubeAwareEncoder { Encoder : enc , Verbose : development } , sink , lvl ))
77
70
log = log .WithOptions (opts ... )
78
71
return zapr .NewLogger (log )
79
72
}
80
73
81
- func fatalIfErr (err error , f func (format string , v ... interface {})) {
74
+ // KubeAwareEncoder is a Kubernetes-aware Zap Encoder.
75
+ // Instead of trying to force Kubernetes objects to implement
76
+ // ObjectMarshaller, we just implement a wrapper around a normal
77
+ // ObjectMarshaller that checks for Kubernetes objects.
78
+ type KubeAwareEncoder struct {
79
+ // Encoder is the zapcore.Encoder that this encoder delegates to
80
+ zapcore.Encoder
81
+
82
+ // Verbose controls whether or not the full object is printed.
83
+ // If false, only name, namespace, api version, and kind are printed.
84
+ // Otherwise, the full object is logged.
85
+ Verbose bool
86
+ }
87
+
88
+ // kubeObjectWrapper is a zapcore.ObjectMarshaller for Kubernetes objects.
89
+ type kubeObjectWrapper struct {
90
+ obj runtime.Object
91
+ key string
92
+ }
93
+
94
+ func (w kubeObjectWrapper ) MarshalLogObject (enc zapcore.ObjectEncoder ) error {
95
+ // TODO(directxman12): log kind and apiversion if not set explicitly (common case)
96
+ // -- needs an a scheme to convert to the GVK.
97
+ gvk := w .obj .GetObjectKind ().GroupVersionKind ()
98
+ if gvk .Version != "" {
99
+ enc .AddString ("apiVersion" , gvk .GroupVersion ().String ())
100
+ enc .AddString ("kind" , gvk .Kind )
101
+ }
102
+
103
+ objMeta , err := meta .Accessor (w .obj )
82
104
if err != nil {
83
- f ("unable to construct the logger: %v" , err )
105
+ return fmt .Errorf ("got runtime.Object without object metadata: %v" , w .obj )
106
+ }
107
+
108
+ ns := objMeta .GetNamespace ()
109
+ if ns != "" {
110
+ enc .AddString ("namespace" , ns )
84
111
}
112
+ enc .AddString ("name" , objMeta .GetName ())
113
+
114
+ return nil
115
+ }
116
+
117
+ // NB(directxman12): can't just override AddReflected, since the encoder calls AddReflected on itself directly
118
+
119
+ func (k * KubeAwareEncoder ) Clone () zapcore.Encoder {
120
+ return & KubeAwareEncoder {
121
+ Encoder : k .Encoder .Clone (),
122
+ }
123
+ }
124
+
125
+ func (k * KubeAwareEncoder ) EncodeEntry (entry zapcore.Entry , fields []zapcore.Field ) (* buffer.Buffer , error ) {
126
+ if k .Verbose {
127
+ // Kubernetes objects implement fmt.Stringer, so if we
128
+ // want verbose output, just delegate to that.
129
+ return k .Encoder .EncodeEntry (entry , fields )
130
+ }
131
+
132
+ for i , field := range fields {
133
+ // intercept stringer fields that happen to be Kubernetes runtime.Object values
134
+ // (Kubernetes runtime.Objects commonly implement String, apparently)
135
+ if field .Type == zapcore .StringerType {
136
+ obj , isObj := field .Interface .(runtime.Object )
137
+ if ! isObj {
138
+ continue
139
+ }
140
+ // TODO(directxman12): is it more performant to do extract the gvk, name, and namespace lazily?
141
+ fields [i ] = zapcore.Field {
142
+ Type : zapcore .ObjectMarshalerType ,
143
+ Key : field .Key ,
144
+ Interface : kubeObjectWrapper {obj : obj },
145
+ }
146
+ }
147
+ }
148
+
149
+ return k .Encoder .EncodeEntry (entry , fields )
85
150
}
86
151
87
152
// SetLogger sets a concrete logging implementation for all deferred Loggers.
0 commit comments