Skip to content

Commit 43351af

Browse files
authored
Merge pull request #237 from DirectXMan12/refactor/logging-audit
✨ Ensure logging is uniform
2 parents 86ad6a3 + 027a76d commit 43351af

File tree

960 files changed

+450
-248099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

960 files changed

+450
-248099
lines changed

Gopkg.lock

Lines changed: 7 additions & 429 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313
# limitations under the License.
1414

1515
# Packages required by users
16+
# NB(directxman12): be very careful how you word these --
17+
# dep considers them as a dependency on the package you list
18+
# meaning that if there's a main.go in the root package
19+
# (like in apiextensions-apiserver), you'll pull it's deps in.
1620
required = ["sigs.k8s.io/testing_frameworks/integration",
1721
"k8s.io/client-go/plugin/pkg/client/auth",
1822
"github.com/spf13/pflag",
1923
"github.com/emicklei/go-restful",
2024
"github.com/go-openapi/spec",
2125
"k8s.io/kube-openapi/pkg/common",
22-
"k8s.io/apiextensions-apiserver",
26+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
27+
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset",
2328
"github.com/prometheus/client_golang/prometheus",
2429
]
2530

TMP-LOGGING.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
Logging Guidelines
2+
==================
3+
4+
controller-runtime uses a kind of logging called *structured logging*. If
5+
you've used a library like Zap or logrus before, you'll be familiar with
6+
the concepts we use. If you've only used a logging library like the "log"
7+
package (in the Go standard library) or "glog" (in Kubernetes), you'll
8+
need to adjust how you think about logging a bit.
9+
10+
### Getting Started With Structured Logging
11+
12+
With structured logging, we associate a *constant* log message with some
13+
variable key-value pairs. For instance, suppose we wanted to log that we
14+
were starting reconciliation on a pod. In the Go standard libary logger,
15+
we might write:
16+
17+
```go
18+
log.Printf("starting reconciliation for pod %s/%s", podNamespace, podName)
19+
```
20+
21+
In controller-runtime, we'd instead write:
22+
23+
```go
24+
logger.Info("starting reconciliation", "pod", req.NamespacedNamed)
25+
```
26+
27+
or even write
28+
29+
```go
30+
func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Response, error) {
31+
logger := logger.WithValues("pod", req.NamespacedName)
32+
// do some stuff
33+
logger.Info("starting reconcilation")
34+
}
35+
```
36+
37+
Notice how we've broken out the information that we want to convey into
38+
a constant message (`"starting reconciliation"`) and some key-value pairs
39+
that convey variable information (`"pod", req.NamespacedName`). We've
40+
there-by added "structure" to our logs, which makes them easier to save
41+
and search later, as well as correlate with metrics and events.
42+
43+
All of controller-runtime's logging is done via
44+
[logr](https://github.com/go-logr/logr), a generic interface for
45+
structured logging. You can use whichever logging library you want to
46+
implement the actual mechanics of the logging. controller-runtime
47+
provides some helpers to make it easy to use
48+
[Zap](https://go.uber.org/zap) as the implementation.
49+
50+
You can configure the logging implementation using
51+
`"sigs.k8s.io/controller-runtime/pkg/runtime/log".SetLogger`. That
52+
package also contains the convinience functions for setting up Zap.
53+
54+
You can get a handle to the the "root" logger using
55+
`"sigs.k8s.io/controller-runtime/pkg/runtime/log".Log`, and can then call
56+
`WithName` to create individual named loggers. You can call `WithName`
57+
repeatedly to chain names together:
58+
59+
```go
60+
logger := log.Log.WithName("controller").WithName("replicaset")
61+
// in reconile...
62+
logger = logger.WithValues("replicaset", req.NamespacedName)
63+
// later on in reconcile...
64+
logger.Info("doing things with pods", "pod", newPod)
65+
```
66+
67+
As seen above, you can also call `WithValue` to create a new sub-logger
68+
that always attaches some key-value pairs to a logger.
69+
70+
Finally, you can use `V(1)` to mark a particular log line as "debug" logs:
71+
72+
```go
73+
logger.V(1).Info("this is particularly verbose!", "state of the world",
74+
allKubernetesObjectsEverywhere)
75+
```
76+
77+
While it's possible to use higher log levels, it's reccomended that you
78+
stick with `V(1)` or V(0)` (which is equivalent to not specifying `V`),
79+
and then filter later based on key-value pairs or messages; different
80+
numbers tend to lose meaning easily over time, and you'll be left
81+
wondering why particular logs lines are at `V(5)` instead of `V(7)`.
82+
83+
## Logging errors
84+
85+
Errors should *always* be logged with `log.Error`, which allows logr
86+
implementations to provide special handling of errors (for instance,
87+
providing stack traces in debug mode).
88+
89+
It's acceptible to log call `log.Error` with a nil error object. This
90+
conveys that an error occurred in some capacity, but that no actual
91+
`error` object was involved.
92+
93+
## Logging messages
94+
95+
- Don't put variable content in your messages -- use key-value pairs for
96+
that. Never use `fmt.Sprintf` in your message.
97+
98+
- Try to match the terminology in your messages with your key-value pairs
99+
-- for instance, if you have a key-value pairs `api version`, use the
100+
term `APIVersion` instead of `GroupVersion` in your message.
101+
102+
## Logging Kubernetes Objects
103+
104+
Kubernetes objects should be logged directly, like `log.Info("this is
105+
a Kubernetes object", "pod", somePod)`. controller-runtime provides
106+
a special encoder for Zap that will transform Kubernetes objects into
107+
`name, namespace, apiVersion, kind` objects, when available and not in
108+
development mode. Other logr implementations should implement similar
109+
logic.
110+
111+
## Logging Structured Values (Key-Value pairs)
112+
113+
- Use lower-case, space separated keys. For example `object` for objects,
114+
`api version` for `APIVersion`
115+
116+
- Be consistent across your application, and with controller-runtime when
117+
possible.
118+
119+
- Try to be brief but descriptive.
120+
121+
- Match terminology in keys with terminology in the message.
122+
123+
- Be careful logging non-Kubernetes objects verbatim if they're very
124+
large.
125+
126+
### Groups, Versions, and Kinds
127+
128+
- Kinds should not be logged alone (they're meanless alone). Use
129+
a `GroupKind` object to log them instead, or a `GroupVersionKind` when
130+
version is relevant.
131+
132+
- If you need to log an API version string, use `api version` as the key
133+
(formatted as with a `GroupVersion`, or as recieved directly from API
134+
discovery).
135+
136+
### Objects and Types
137+
138+
- If code works with a generic Kubernetes `runtime.Object`, use the
139+
`object` key. For specific objects, prefer the resource name as the key
140+
(e.g. `pod` for `v1.Pod` objects).
141+
142+
- For non-Kubernetes objects, the `object` key may also be used, if you
143+
accept a generic interface.
144+
145+
- When logging a raw type, log it using the `type` key, with a value of
146+
`fmt.Sprintf("%T", typ)`
147+
148+
- If there's specific context around a type, the key may be more specific,
149+
but should end with `type` -- for instance, `OwnerType` should be logged
150+
as `owner` in the context of `log.Error(err, "Could not get ObjectKinds
151+
for OwnerType", `owner type`, fmt.Sprintf("%T"))`. When possible, favor
152+
communicating kind instead.
153+
154+
### Multiple things
155+
156+
- When logging multiple things, simply pluralize the key.
157+
158+
### controller-runtime Specifics
159+
160+
- Reconcile requests should be logged as `request`, although normal code
161+
should favor logging the key.
162+
163+
- Reconcile keys should be logged as with the same key as if you were
164+
logging the object directly (e.g. `log.Info("reconciling pod", "pod",
165+
req.NamespacedName)`). This ends up having a similar effect to logging
166+
the object directly.

pkg/client/fake/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.O
5959
for _, obj := range initObjs {
6060
err := tracker.Add(obj)
6161
if err != nil {
62-
log.Error(err, "failed to add object", "object", obj)
62+
log.Error(err, "failed to add object to fake client", "object", obj)
6363
os.Exit(1)
6464
return nil
6565
}

pkg/handler/enqueue.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type EnqueueRequestForObject struct{}
3636
// Create implements EventHandler
3737
func (e *EnqueueRequestForObject) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
3838
if evt.Meta == nil {
39-
enqueueLog.Error(nil, "CreateEvent received with no metadata", "CreateEvent", evt)
39+
enqueueLog.Error(nil, "CreateEvent received with no metadata", "event", evt)
4040
return
4141
}
4242
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
@@ -53,7 +53,7 @@ func (e *EnqueueRequestForObject) Update(evt event.UpdateEvent, q workqueue.Rate
5353
Namespace: evt.MetaOld.GetNamespace(),
5454
}})
5555
} else {
56-
enqueueLog.Error(nil, "UpdateEvent received with no old metadata", "UpdateEvent", evt)
56+
enqueueLog.Error(nil, "UpdateEvent received with no old metadata", "event", evt)
5757
}
5858

5959
if evt.MetaNew != nil {
@@ -62,14 +62,14 @@ func (e *EnqueueRequestForObject) Update(evt event.UpdateEvent, q workqueue.Rate
6262
Namespace: evt.MetaNew.GetNamespace(),
6363
}})
6464
} else {
65-
enqueueLog.Error(nil, "UpdateEvent received with no new metadata", "UpdateEvent", evt)
65+
enqueueLog.Error(nil, "UpdateEvent received with no new metadata", "event", evt)
6666
}
6767
}
6868

6969
// Delete implements EventHandler
7070
func (e *EnqueueRequestForObject) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
7171
if evt.Meta == nil {
72-
enqueueLog.Error(nil, "DeleteEvent received with no metadata", "DeleteEvent", evt)
72+
enqueueLog.Error(nil, "DeleteEvent received with no metadata", "event", evt)
7373
return
7474
}
7575
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
@@ -81,7 +81,7 @@ func (e *EnqueueRequestForObject) Delete(evt event.DeleteEvent, q workqueue.Rate
8181
// Generic implements EventHandler
8282
func (e *EnqueueRequestForObject) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) {
8383
if evt.Meta == nil {
84-
enqueueLog.Error(nil, "GenericEvent received with no metadata", "GenericEvent", evt)
84+
enqueueLog.Error(nil, "GenericEvent received with no metadata", "event", evt)
8585
return
8686
}
8787
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{

pkg/handler/enqueue_owner.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ func (e *EnqueueRequestForOwner) parseOwnerTypeGroupKind(scheme *runtime.Scheme)
9090
// Get the kinds of the type
9191
kinds, _, err := scheme.ObjectKinds(e.OwnerType)
9292
if err != nil {
93-
log.Error(err, "Could not get ObjectKinds for OwnerType", "OwnerType", e.OwnerType)
93+
log.Error(err, "Could not get ObjectKinds for OwnerType", "owner type", fmt.Sprintf("%T", e.OwnerType))
9494
return err
9595
}
9696
// Expect only 1 kind. If there is more than one kind this is probably an edge case such as ListOptions.
9797
if len(kinds) != 1 {
9898
err := fmt.Errorf("Expected exactly 1 kind for OwnerType %T, but found %s kinds", e.OwnerType, kinds)
99-
log.Error(err, "", "OwnerType", e.OwnerType, "Kinds", kinds)
99+
log.Error(nil, "Expected exactly 1 kind for OwnerType", "owner type", fmt.Sprintf("%T", e.OwnerType), "kinds", kinds)
100100
return err
101101

102102
}
@@ -115,8 +115,8 @@ func (e *EnqueueRequestForOwner) getOwnerReconcileRequest(object metav1.Object)
115115
// Parse the Group out of the OwnerReference to compare it to what was parsed out of the requested OwnerType
116116
refGV, err := schema.ParseGroupVersion(ref.APIVersion)
117117
if err != nil {
118-
log.Error(err, "Could not parse OwnerReference GroupVersion",
119-
"OwnerReference", ref.APIVersion)
118+
log.Error(err, "Could not parse OwnerReference APIVersion",
119+
"api version", ref.APIVersion)
120120
return nil
121121
}
122122

pkg/internal/admission/http.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (h httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4242
// verify the content type is accurate
4343
contentType := r.Header.Get("Content-Type")
4444
if contentType != "application/json" {
45-
log.Error(nil, "invalid content type, expected application/json", "context type", contentType)
45+
log.Error(nil, "invalid content type, expected application/json", "content type", contentType)
4646
return
4747
}
4848

pkg/internal/controller/controller.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
118118
}
119119
}
120120

121-
log.Info("Starting EventSource", "Controller", c.Name, "Source", src)
121+
log.Info("Starting EventSource", "controller", c.Name, "source", src)
122122
return src.Start(evthdler, c.Queue, prct...)
123123
}
124124

@@ -131,7 +131,7 @@ func (c *Controller) Start(stop <-chan struct{}) error {
131131
defer c.Queue.ShutDown()
132132

133133
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
134-
log.Info("Starting Controller", "Controller", c.Name)
134+
log.Info("Starting Controller", "controller", c.Name)
135135

136136
// Wait for the caches to be synced before starting workers
137137
if c.WaitForCacheSync == nil {
@@ -141,7 +141,7 @@ func (c *Controller) Start(stop <-chan struct{}) error {
141141
// This code is unreachable right now since WaitForCacheSync will never return an error
142142
// Leaving it here because that could happen in the future
143143
err := fmt.Errorf("failed to wait for %s caches to sync", c.Name)
144-
log.Error(err, "Could not wait for Cache to sync", "Controller", c.Name)
144+
log.Error(err, "Could not wait for Cache to sync", "controller", c.Name)
145145
c.mu.Unlock()
146146
return err
147147
}
@@ -151,7 +151,7 @@ func (c *Controller) Start(stop <-chan struct{}) error {
151151
}
152152

153153
// Launch workers to process resources
154-
log.Info("Starting workers", "Controller", c.Name, "WorkerCount", c.MaxConcurrentReconciles)
154+
log.Info("Starting workers", "controller", c.Name, "worker count", c.MaxConcurrentReconciles)
155155
for i := 0; i < c.MaxConcurrentReconciles; i++ {
156156
// Process work items
157157
go wait.Until(func() {
@@ -164,7 +164,7 @@ func (c *Controller) Start(stop <-chan struct{}) error {
164164
c.mu.Unlock()
165165

166166
<-stop
167-
log.Info("Stopping workers", "Controller", c.Name)
167+
log.Info("Stopping workers", "controller", c.Name)
168168
return nil
169169
}
170170

@@ -203,7 +203,7 @@ func (c *Controller) processNextWorkItem() bool {
203203
// process a work item that is invalid.
204204
c.Queue.Forget(obj)
205205
log.Error(nil, "Queue item was not a Request",
206-
"Controller", c.Name, "Type", fmt.Sprintf("%T", obj), "Value", obj)
206+
"controller", c.Name, "type", fmt.Sprintf("%T", obj), "value", obj)
207207
// Return true, don't take a break
208208
return true
209209
}
@@ -212,7 +212,7 @@ func (c *Controller) processNextWorkItem() bool {
212212
// resource to be synced.
213213
if result, err := c.Do.Reconcile(req); err != nil {
214214
c.Queue.AddRateLimited(req)
215-
log.Error(err, "Reconciler error", "Controller", c.Name, "Request", req)
215+
log.Error(err, "Reconciler error", "controller", c.Name, "request", req)
216216
ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
217217

218218
return false
@@ -229,7 +229,7 @@ func (c *Controller) processNextWorkItem() bool {
229229
c.Queue.Forget(obj)
230230

231231
// TODO(directxman12): What does 1 mean? Do we want level constants? Do we want levels at all?
232-
log.V(1).Info("Successfully Reconciled", "Controller", c.Name, "Request", req)
232+
log.V(1).Info("Successfully Reconciled", "controller", c.Name, "request", req)
233233

234234
// Return true, don't take a break
235235
return true

pkg/predicate/predicate.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,19 @@ type ResourceVersionChangedPredicate struct {
9696
// Update implements default UpdateEvent filter for validating resource version change
9797
func (ResourceVersionChangedPredicate) Update(e event.UpdateEvent) bool {
9898
if e.MetaOld == nil {
99-
log.Error(nil, "UpdateEvent has no old metadata", "UpdateEvent", e)
99+
log.Error(nil, "UpdateEvent has no old metadata", "event", e)
100100
return false
101101
}
102102
if e.ObjectOld == nil {
103-
log.Error(nil, "GenericEvent has no old runtime object to update", "GenericEvent", e)
103+
log.Error(nil, "GenericEvent has no old runtime object to update", "event", e)
104104
return false
105105
}
106106
if e.ObjectNew == nil {
107-
log.Error(nil, "GenericEvent has no new runtime object for update", "GenericEvent", e)
107+
log.Error(nil, "GenericEvent has no new runtime object for update", "event", e)
108108
return false
109109
}
110110
if e.MetaNew == nil {
111-
log.Error(nil, "UpdateEvent has no new metadata", "UpdateEvent", e)
111+
log.Error(nil, "UpdateEvent has no new metadata", "event", e)
112112
return false
113113
}
114114
if e.MetaNew.GetResourceVersion() == e.MetaOld.GetResourceVersion() {

0 commit comments

Comments
 (0)