@@ -18,14 +18,19 @@ package controller
1818
1919import (
2020 "context"
21+ "encoding/hex"
2122 "errors"
2223 "fmt"
24+ "math/rand"
25+ "strings"
2326 "sync"
2427 "time"
2528
2629 "github.com/go-logr/logr"
30+ "k8s.io/apimachinery/pkg/runtime/schema"
2731 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2832 "k8s.io/client-go/util/workqueue"
33+
2934 "sigs.k8s.io/controller-runtime/pkg/handler"
3035 ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
3136 logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -86,6 +91,13 @@ type Controller struct {
8691 // Log is used to log messages to users during reconciliation, or for example when a watch is started.
8792 Log logr.Logger
8893
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+
89101 // RecoverPanic indicates whether the panic caused by reconcile should be recovered.
90102 RecoverPanic bool
91103}
@@ -99,7 +111,6 @@ type watchDescription struct {
99111
100112// Reconcile implements reconcile.Reconciler.
101113func (c * Controller ) Reconcile (ctx context.Context , req reconcile.Request ) (_ reconcile.Result , err error ) {
102- log := c .Log .WithValues ("name" , req .Name , "namespace" , req .Namespace )
103114 defer func () {
104115 if r := recover (); r != nil {
105116 if c .RecoverPanic {
@@ -110,11 +121,11 @@ func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ re
110121 return
111122 }
112123
124+ log := logf .FromContext (ctx )
113125 log .Info (fmt .Sprintf ("Observed a panic in reconciler: %v" , r ))
114126 panic (r )
115127 }
116128 }()
117- ctx = logf .IntoContext (ctx , log )
118129 return c .Do .Reconcile (ctx , req )
119130}
120131
@@ -295,7 +306,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
295306 c .updateMetrics (time .Since (reconcileStartTS ))
296307 }()
297308
298- // Make sure that the the object is a valid request.
309+ // Make sure that the object is a valid request.
299310 req , ok := obj .(reconcile.Request )
300311 if ! ok {
301312 // As the item in the workqueue is actually invalid, we call
@@ -307,7 +318,24 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
307318 return
308319 }
309320
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 )
311339 ctx = logf .IntoContext (ctx , log )
312340
313341 // RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
@@ -353,3 +381,46 @@ func (c *Controller) InjectFunc(f inject.Func) error {
353381func (c * Controller ) updateMetrics (reconcileTime time.Duration ) {
354382 ctrlmetrics .ReconcileTime .WithLabelValues (c .Name ).Observe (reconcileTime .Seconds ())
355383}
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