Skip to content

Commit 598978c

Browse files
committed
logging: align to Kubernetes structured logging, add reconcileID
Signed-off-by: Stefan Büringer [email protected]
1 parent 9ee63fc commit 598978c

File tree

6 files changed

+75
-33
lines changed

6 files changed

+75
-33
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
k8s.io/apimachinery v0.23.0
2222
k8s.io/client-go v0.23.0
2323
k8s.io/component-base v0.23.0
24+
k8s.io/klog/v2 v2.30.0
2425
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
2526
sigs.k8s.io/yaml v1.3.0
2627
)
@@ -59,7 +60,6 @@ require (
5960
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
6061
gopkg.in/yaml.v2 v2.4.0 // indirect
6162
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
62-
k8s.io/klog/v2 v2.30.0 // indirect
6363
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
6464
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
6565
sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect

pkg/builder/controller.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/go-logr/logr"
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2525
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/klog/v2"
2627

2728
"sigs.k8s.io/controller-runtime/pkg/client"
2829
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -148,9 +149,9 @@ func (blder *Builder) WithOptions(options controller.Options) *Builder {
148149
return blder
149150
}
150151

151-
// WithLogger overrides the controller options's logger used.
152-
func (blder *Builder) WithLogger(log logr.Logger) *Builder {
153-
blder.ctrlOptions.Log = log
152+
// WithLogConstructor overrides the controller options's LogConstructor.
153+
func (blder *Builder) WithLogConstructor(logConstructor func(*reconcile.Request) logr.Logger) *Builder {
154+
blder.ctrlOptions.LogConstructor = logConstructor
154155
return blder
155156
}
156157

@@ -304,13 +305,31 @@ func (blder *Builder) doController(r reconcile.Reconciler) error {
304305
ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout
305306
}
306307

308+
controllerName := blder.getControllerName(gvk)
309+
307310
// Setup the logger.
308-
if ctrlOptions.Log.GetSink() == nil {
309-
ctrlOptions.Log = blder.mgr.GetLogger()
311+
if ctrlOptions.LogConstructor == nil {
312+
log = blder.mgr.GetLogger().WithValues(
313+
"controller", controllerName,
314+
"controllerGroup", gvk.Group,
315+
"controllerKind", gvk.Kind,
316+
)
317+
318+
lowerCamelCaseKind := strings.ToLower(gvk.Kind[:1]) + gvk.Kind[1:]
319+
320+
ctrlOptions.LogConstructor = func(req *reconcile.Request) logr.Logger {
321+
log := log
322+
if req != nil {
323+
log = log.WithValues(
324+
lowerCamelCaseKind, klog.KRef(req.Namespace, req.Name),
325+
"namespace", req.Namespace, "name", req.Name,
326+
)
327+
}
328+
return log
329+
}
310330
}
311-
ctrlOptions.Log = ctrlOptions.Log.WithValues("reconciler group", gvk.Group, "reconciler kind", gvk.Kind)
312331

313332
// Build the controller and return.
314-
blder.ctrl, err = newController(blder.getControllerName(gvk), blder.mgr, ctrlOptions)
333+
blder.ctrl, err = newController(controllerName, blder.mgr, ctrlOptions)
315334
return err
316335
}

pkg/builder/controller_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,10 @@ var _ = Describe("application", func() {
238238

239239
logger := &testLogger{}
240240
newController = func(name string, mgr manager.Manager, options controller.Options) (controller.Controller, error) {
241-
if options.Log.GetSink() == logger {
241+
if options.LogConstructor(nil).GetSink() == logger {
242242
return controller.New(name, mgr, options)
243243
}
244-
return nil, fmt.Errorf("logger expected %T but found %T", logger, options.Log)
244+
return nil, fmt.Errorf("logger expected %T but found %T", logger, options.LogConstructor)
245245
}
246246

247247
By("creating a controller manager")
@@ -251,7 +251,9 @@ var _ = Describe("application", func() {
251251
instance, err := ControllerManagedBy(m).
252252
For(&appsv1.ReplicaSet{}).
253253
Owns(&appsv1.ReplicaSet{}).
254-
WithLogger(logr.New(logger)).
254+
WithLogConstructor(func(request *reconcile.Request) logr.Logger {
255+
return logr.New(logger)
256+
}).
255257
Build(noop)
256258
Expect(err).NotTo(HaveOccurred())
257259
Expect(instance).NotTo(BeNil())

pkg/controller/controller.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/go-logr/logr"
2525
"k8s.io/client-go/util/workqueue"
26+
"k8s.io/klog/v2"
2627

2728
"sigs.k8s.io/controller-runtime/pkg/handler"
2829
"sigs.k8s.io/controller-runtime/pkg/internal/controller"
@@ -46,9 +47,9 @@ type Options struct {
4647
// The overall is a token bucket and the per-item is exponential.
4748
RateLimiter ratelimiter.RateLimiter
4849

49-
// Log is the logger used for this controller and passed to each reconciliation
50-
// request via the context field.
51-
Log logr.Logger
50+
// LogConstructor is used to construct a logger used for this controller and passed
51+
// to each reconciliation via the context field.
52+
LogConstructor func(request *reconcile.Request) logr.Logger
5253

5354
// CacheSyncTimeout refers to the time limit set to wait for syncing caches.
5455
// Defaults to 2 minutes if not set.
@@ -105,8 +106,20 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
105106
return nil, fmt.Errorf("must specify Name for Controller")
106107
}
107108

108-
if options.Log.GetSink() == nil {
109-
options.Log = mgr.GetLogger()
109+
if options.LogConstructor == nil {
110+
log := mgr.GetLogger().WithValues(
111+
"controller", name,
112+
)
113+
options.LogConstructor = func(req *reconcile.Request) logr.Logger {
114+
log := log
115+
if req != nil {
116+
log = log.WithValues(
117+
"object", klog.KRef(req.Namespace, req.Name),
118+
"namespace", req.Namespace, "name", req.Name,
119+
)
120+
}
121+
return log
122+
}
110123
}
111124

112125
if options.MaxConcurrentReconciles <= 0 {
@@ -136,7 +149,7 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
136149
CacheSyncTimeout: options.CacheSyncTimeout,
137150
SetFields: mgr.SetFields,
138151
Name: name,
139-
Log: options.Log.WithName("controller").WithName(name).WithValues("controller", name),
152+
LogConstructor: options.LogConstructor,
140153
RecoverPanic: options.RecoverPanic,
141154
}, nil
142155
}

pkg/internal/controller/controller.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/go-logr/logr"
2727
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
28+
"k8s.io/apimachinery/pkg/util/uuid"
2829
"k8s.io/client-go/util/workqueue"
2930
"sigs.k8s.io/controller-runtime/pkg/handler"
3031
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
@@ -83,8 +84,11 @@ type Controller struct {
8384
// startWatches maintains a list of sources, handlers, and predicates to start when the controller is started.
8485
startWatches []watchDescription
8586

86-
// Log is used to log messages to users during reconciliation, or for example when a watch is started.
87-
Log logr.Logger
87+
// LogConstructor is used to construct a logger to then log messages to users during reconciliation,
88+
// or for example when a watch is started.
89+
// Note: LogConstructor has to be able to handle nil requests as we are also using it
90+
// outside the context of a reconciliation.
91+
LogConstructor func(request *reconcile.Request) logr.Logger
8892

8993
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
9094
RecoverPanic bool
@@ -99,7 +103,6 @@ type watchDescription struct {
99103

100104
// Reconcile implements reconcile.Reconciler.
101105
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)
103106
defer func() {
104107
if r := recover(); r != nil {
105108
if c.RecoverPanic {
@@ -110,11 +113,11 @@ func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ re
110113
return
111114
}
112115

116+
log := logf.FromContext(ctx)
113117
log.Info(fmt.Sprintf("Observed a panic in reconciler: %v", r))
114118
panic(r)
115119
}
116120
}()
117-
ctx = logf.IntoContext(ctx, log)
118121
return c.Do.Reconcile(ctx, req)
119122
}
120123

@@ -144,7 +147,7 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
144147
return nil
145148
}
146149

147-
c.Log.Info("Starting EventSource", "source", src)
150+
c.LogConstructor(nil).Info("Starting EventSource", "source", src)
148151
return src.Start(c.ctx, evthdler, c.Queue, prct...)
149152
}
150153

@@ -179,15 +182,15 @@ func (c *Controller) Start(ctx context.Context) error {
179182
// caches to sync so that they have a chance to register their intendeded
180183
// caches.
181184
for _, watch := range c.startWatches {
182-
c.Log.Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))
185+
c.LogConstructor(nil).Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))
183186

184187
if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
185188
return err
186189
}
187190
}
188191

189192
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
190-
c.Log.Info("Starting Controller")
193+
c.LogConstructor(nil).Info("Starting Controller")
191194

192195
for _, watch := range c.startWatches {
193196
syncingSource, ok := watch.src.(source.SyncingSource)
@@ -204,7 +207,7 @@ func (c *Controller) Start(ctx context.Context) error {
204207
// is an error or a timeout
205208
if err := syncingSource.WaitForSync(sourceStartCtx); err != nil {
206209
err := fmt.Errorf("failed to wait for %s caches to sync: %w", c.Name, err)
207-
c.Log.Error(err, "Could not wait for Cache to sync")
210+
c.LogConstructor(nil).Error(err, "Could not wait for Cache to sync")
208211
return err
209212
}
210213

@@ -221,7 +224,7 @@ func (c *Controller) Start(ctx context.Context) error {
221224
c.startWatches = nil
222225

223226
// Launch workers to process resources
224-
c.Log.Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
227+
c.LogConstructor(nil).Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
225228
wg.Add(c.MaxConcurrentReconciles)
226229
for i := 0; i < c.MaxConcurrentReconciles; i++ {
227230
go func() {
@@ -241,9 +244,9 @@ func (c *Controller) Start(ctx context.Context) error {
241244
}
242245

243246
<-ctx.Done()
244-
c.Log.Info("Shutdown signal received, waiting for all workers to finish")
247+
c.LogConstructor(nil).Info("Shutdown signal received, waiting for all workers to finish")
245248
wg.Wait()
246-
c.Log.Info("All workers finished")
249+
c.LogConstructor(nil).Info("All workers finished")
247250
return nil
248251
}
249252

@@ -295,19 +298,21 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
295298
c.updateMetrics(time.Since(reconcileStartTS))
296299
}()
297300

298-
// Make sure that the the object is a valid request.
301+
// Make sure that the object is a valid request.
299302
req, ok := obj.(reconcile.Request)
300303
if !ok {
301304
// As the item in the workqueue is actually invalid, we call
302305
// Forget here else we'd go into a loop of attempting to
303306
// process a work item that is invalid.
304307
c.Queue.Forget(obj)
305-
c.Log.Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
308+
c.LogConstructor(nil).Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
306309
// Return true, don't take a break
307310
return
308311
}
309312

310-
log := c.Log.WithValues("name", req.Name, "namespace", req.Namespace)
313+
log := c.LogConstructor(&req)
314+
315+
log = log.WithValues("reconcileID", uuid.NewUUID())
311316
ctx = logf.IntoContext(ctx, log)
312317

313318
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
@@ -340,7 +345,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
340345

341346
// GetLogger returns this controller's logger.
342347
func (c *Controller) GetLogger() logr.Logger {
343-
return c.Log
348+
return c.LogConstructor(nil)
344349
}
345350

346351
// InjectFunc implement SetFields.Injector.

pkg/internal/controller/controller_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sync"
2424
"time"
2525

26+
"github.com/go-logr/logr"
2627
. "github.com/onsi/ginkgo"
2728
. "github.com/onsi/gomega"
2829
"github.com/prometheus/client_golang/prometheus"
@@ -70,7 +71,9 @@ var _ = Describe("controller", func() {
7071
MaxConcurrentReconciles: 1,
7172
Do: fakeReconcile,
7273
MakeQueue: func() workqueue.RateLimitingInterface { return queue },
73-
Log: log.RuntimeLog.WithName("controller").WithName("test"),
74+
LogConstructor: func(_ *reconcile.Request) logr.Logger {
75+
return log.RuntimeLog.WithName("controller").WithName("test")
76+
},
7477
}
7578
Expect(ctrl.InjectFunc(func(interface{}) error { return nil })).To(Succeed())
7679
})

0 commit comments

Comments
 (0)