Skip to content

Commit 2266c83

Browse files
committed
logging: align to Kubernetes structured logging, add reconcileID
Signed-off-by: Stefan Büringer [email protected]
1 parent eb292e5 commit 2266c83

File tree

3 files changed

+99
-10
lines changed

3 files changed

+99
-10
lines changed

pkg/builder/controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,8 @@ func (blder *Builder) doController(r reconcile.Reconciler) error {
308308
if ctrlOptions.Log.GetSink() == nil {
309309
ctrlOptions.Log = blder.mgr.GetLogger()
310310
}
311-
ctrlOptions.Log = ctrlOptions.Log.WithValues("reconciler group", gvk.Group, "reconciler kind", gvk.Kind)
312311

313312
// Build the controller and return.
314-
blder.ctrl, err = newController(blder.getControllerName(gvk), blder.mgr, ctrlOptions)
313+
blder.ctrl, err = newController(blder.getControllerName(gvk), &gvk, blder.mgr, ctrlOptions)
315314
return err
316315
}

pkg/controller/controller.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ package controller
1818

1919
import (
2020
"context"
21+
crand "crypto/rand"
22+
"encoding/binary"
2123
"fmt"
24+
"math/rand"
2225
"time"
2326

2427
"github.com/go-logr/logr"
28+
"k8s.io/apimachinery/pkg/runtime/schema"
2529
"k8s.io/client-go/util/workqueue"
2630

2731
"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -84,8 +88,8 @@ type Controller interface {
8488

8589
// New returns a new Controller registered with the Manager. The Manager will ensure that shared Caches have
8690
// been synced before the Controller is Started.
87-
func New(name string, mgr manager.Manager, options Options) (Controller, error) {
88-
c, err := NewUnmanaged(name, mgr, options)
91+
func New(name string, gvk *schema.GroupVersionKind, mgr manager.Manager, options Options) (Controller, error) {
92+
c, err := NewUnmanaged(name, gvk, mgr, options)
8993
if err != nil {
9094
return nil, err
9195
}
@@ -96,7 +100,7 @@ func New(name string, mgr manager.Manager, options Options) (Controller, error)
96100

97101
// NewUnmanaged returns a new controller without adding it to the manager. The
98102
// caller is responsible for starting the returned controller.
99-
func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller, error) {
103+
func NewUnmanaged(name string, gvk *schema.GroupVersionKind, mgr manager.Manager, options Options) (Controller, error) {
100104
if options.Reconciler == nil {
101105
return nil, fmt.Errorf("must specify Reconciler")
102106
}
@@ -126,6 +130,19 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
126130
return nil, err
127131
}
128132

133+
// Add controller and reconciler group / kind to logger.
134+
log := options.Log.WithValues("controller", name)
135+
if gvk != nil {
136+
log = log.WithValues("reconciler group", gvk.Group, "reconciler kind", gvk.Kind)
137+
}
138+
139+
// Initialize random source, later used to generate reconcileIDs.
140+
var rngSeed int64
141+
if err := binary.Read(crand.Reader, binary.LittleEndian, &rngSeed); err != nil {
142+
return nil, fmt.Errorf("could not read random bytes to seed random source for reconcileID generation: %v", err)
143+
}
144+
randSource := rand.New(rand.NewSource(rngSeed))
145+
129146
// Create controller with dependencies set
130147
return &controller.Controller{
131148
Do: options.Reconciler,
@@ -136,7 +153,9 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
136153
CacheSyncTimeout: options.CacheSyncTimeout,
137154
SetFields: mgr.SetFields,
138155
Name: name,
139-
Log: options.Log.WithName("controller").WithName(name).WithValues("controller", name),
156+
GVK: gvk,
157+
Log: log,
158+
RandSource: randSource,
140159
RecoverPanic: options.RecoverPanic,
141160
}, nil
142161
}

pkg/internal/controller/controller.go

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ package controller
1818

1919
import (
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.
101113
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)
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 {
353381
func (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

Comments
 (0)