@@ -18,14 +18,19 @@ package controller
18
18
19
19
import (
20
20
"context"
21
+ "encoding/hex"
21
22
"errors"
22
23
"fmt"
24
+ "math/rand"
25
+ "strings"
23
26
"sync"
24
27
"time"
25
28
26
29
"github.com/go-logr/logr"
30
+ "k8s.io/apimachinery/pkg/runtime/schema"
27
31
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
28
32
"k8s.io/client-go/util/workqueue"
33
+
29
34
"sigs.k8s.io/controller-runtime/pkg/handler"
30
35
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
31
36
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -86,6 +91,13 @@ type Controller struct {
86
91
// Log is used to log messages to users during reconciliation, or for example when a watch is started.
87
92
Log logr.Logger
88
93
94
+ // RandSource is used to generate reconcileIDs for logging.
95
+ RandSource * rand.Rand
96
+
97
+ // GVK is used to create the log key for the object.
98
+ // If not set, "obj" is used instead.
99
+ GVK * schema.GroupVersionKind
100
+
89
101
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
90
102
RecoverPanic bool
91
103
}
@@ -99,7 +111,6 @@ type watchDescription struct {
99
111
100
112
// Reconcile implements reconcile.Reconciler.
101
113
func (c * Controller ) Reconcile (ctx context.Context , req reconcile.Request ) (_ reconcile.Result , err error ) {
102
- log := c .Log .WithValues ("name" , req .Name , "namespace" , req .Namespace )
103
114
defer func () {
104
115
if r := recover (); r != nil {
105
116
if c .RecoverPanic {
@@ -110,11 +121,11 @@ func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ re
110
121
return
111
122
}
112
123
124
+ log := logf .FromContext (ctx )
113
125
log .Info (fmt .Sprintf ("Observed a panic in reconciler: %v" , r ))
114
126
panic (r )
115
127
}
116
128
}()
117
- ctx = logf .IntoContext (ctx , log )
118
129
return c .Do .Reconcile (ctx , req )
119
130
}
120
131
@@ -295,7 +306,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
295
306
c .updateMetrics (time .Since (reconcileStartTS ))
296
307
}()
297
308
298
- // Make sure that the the object is a valid request.
309
+ // Make sure that the object is a valid request.
299
310
req , ok := obj .(reconcile.Request )
300
311
if ! ok {
301
312
// As the item in the workqueue is actually invalid, we call
@@ -307,7 +318,24 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
307
318
return
308
319
}
309
320
310
- log := c .Log .WithValues ("name" , req .Name , "namespace" , req .Namespace )
321
+ // Add object to the logger.
322
+ var objectLogKey = "obj"
323
+ if c .GVK != nil {
324
+ objectLogKey = strings .ToLower (c .GVK .Kind )
325
+ }
326
+ log := c .Log .WithValues (objectLogKey , KRef (req .Namespace , req .Name ))
327
+
328
+ // Add reconcileID to the logger.
329
+ reconcileID , err := c .generateReconcileID ()
330
+ if err != nil {
331
+ c .Queue .AddRateLimited (req )
332
+ ctrlmetrics .ReconcileErrors .WithLabelValues (c .Name ).Inc ()
333
+ ctrlmetrics .ReconcileTotal .WithLabelValues (c .Name , labelError ).Inc ()
334
+ log .Error (err , "Reconciler error" )
335
+ return
336
+ }
337
+
338
+ log = log .WithValues ("reconcileID" , reconcileID )
311
339
ctx = logf .IntoContext (ctx , log )
312
340
313
341
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
@@ -353,3 +381,46 @@ func (c *Controller) InjectFunc(f inject.Func) error {
353
381
func (c * Controller ) updateMetrics (reconcileTime time.Duration ) {
354
382
ctrlmetrics .ReconcileTime .WithLabelValues (c .Name ).Observe (reconcileTime .Seconds ())
355
383
}
384
+
385
+ // KRef returns ObjectRef from name and namespace
386
+ // Note: This is a copy of the func from klog. It has been copied to avoid
387
+ // introducing a dependency to klog, while still implement logging according
388
+ // to the Kubernetes structured logging KEP.
389
+ func KRef (namespace , name string ) ObjectRef {
390
+ return ObjectRef {
391
+ Name : name ,
392
+ Namespace : namespace ,
393
+ }
394
+ }
395
+
396
+ // ObjectRef references a kubernetes object
397
+ // Note: This is a copy of the struct from klog. It has been copied to avoid
398
+ // introducing a dependency to klog, while still implement logging according
399
+ // to the Kubernetes structured logging KEP.
400
+ type ObjectRef struct {
401
+ Name string `json:"name"`
402
+ Namespace string `json:"namespace,omitempty"`
403
+ }
404
+
405
+ // MarshalLog ensures that loggers with support for structured output will log
406
+ // as a struct by removing the String method via a custom type.
407
+ func (ref ObjectRef ) MarshalLog () interface {} {
408
+ type or ObjectRef
409
+ return or (ref )
410
+ }
411
+
412
+ func (ref ObjectRef ) String () string {
413
+ if ref .Namespace != "" {
414
+ return fmt .Sprintf ("%s/%s" , ref .Namespace , ref .Name )
415
+ }
416
+ return ref .Name
417
+ }
418
+
419
+ // generateReconcileID generates a reconcileID for logging.
420
+ func (c * Controller ) generateReconcileID () (string , error ) {
421
+ id := [16 ]byte {}
422
+ if _ , err := c .RandSource .Read (id [:]); err != nil {
423
+ return "" , fmt .Errorf ("failed to generate reconcileID: %v" , err )
424
+ }
425
+ return hex .EncodeToString (id [:]), nil
426
+ }
0 commit comments