Skip to content

Commit 6df2857

Browse files
committed
doc/dev/logging.md: discuss how to use logging in SDK operators
1 parent 5eb3e66 commit 6df2857

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

doc/dev/logging.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Logging in operators
2+
3+
Operator SDK-generated operators use the [`logr`][godoc_logr] interface to log. This log interface has several backends such as [`zap`][repo_zapr], which the SDK uses in generated code by default. [`logr.Logger`][godoc_logr_logger] exposes [structured logging][site_struct_logging] methods that help create machine-readable logs and adding a wealth of information to log records.
4+
5+
## Setting the logger
6+
7+
Operators set the logger for all operator logging in [`cmd/manager/main.go`][code_set_logger]:
8+
9+
```Go
10+
import (
11+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
12+
)
13+
14+
func main() {
15+
logf.SetLogger(logf.ZapLogger(false))
16+
log := logf.Log.WithName("cmd")
17+
18+
...
19+
20+
log.Info("Starting the Cmd.")
21+
22+
...
23+
}
24+
```
25+
26+
By using `controller-runtime/pkg/runtime/log`, your logger is propagated through `controller-runtime`. Any logs produced by `controller-runtime` code will be through your logger, and therefore have the same formatting and destination.
27+
28+
In the above example, `logf.ZapLogger()` takes a boolean flag to set development parameters. Passing in `true` will set the logger to log in development mode; debug log statements will trigger, and error log statements will include stack traces.
29+
30+
## Creating a structured log statement
31+
32+
There are two ways to create structured logs with `logr`. You can create new loggers using `log.WithValues(keyValues)` that include `keyValues`, a list of key-value pair `interface{}`'s, in each log record. Alternatively you can include `keyValues` directly in a log statement, as all `logr` log statements take some message and `keyValues`. The signature of `logr.Error()` has an `error`-type parameter, which can be `nil`.
33+
34+
An example from [`memcached_controller.go`][code_memcached_controller]:
35+
36+
```Go
37+
package memcached
38+
39+
import (
40+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
41+
)
42+
43+
// Set a global logger for the memcached package. Each log record produced
44+
// by this logger will have an identifier containing "controller_memcached".
45+
// These names are hierarchical; the name attached to memcached log statements
46+
// will be "operator-sdk.controller_memcached" because SDKLog has name
47+
// "operator-sdk".
48+
var log = logf.Log.WithName("controller_memcached")
49+
50+
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) {
51+
// Create a logger for Reconcile() that includes "Request.Namespace"
52+
// and "Request.Name" in each log record from this log statement.
53+
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
54+
reqLogger.Info("Reconciling Memcached.")
55+
56+
memcached := &cachev1alpha1.Memcached{}
57+
err := r.client.Get(context.TODO(), request.NamespacedName, memcached)
58+
if err != nil {
59+
if errors.IsNotFound(err) {
60+
reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted.")
61+
return reconcile.Result{}, nil
62+
}
63+
return reconcile.Result{}, err
64+
}
65+
66+
found := &appsv1.Deployment{}
67+
err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
68+
if err != nil {
69+
if errors.IsNotFound(err) {
70+
dep := r.deploymentForMemcached(memcached)
71+
// Include "Deployment.Namespace" and "Deployment.Name" in records
72+
// produced by this particular log statement. "Request.Namespace" and
73+
// "Request.Name" will also be included from reqLogger.
74+
reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
75+
err = r.client.Create(context.TODO(), dep)
76+
if err != nil {
77+
// Include the error in records produced by this log statement.
78+
reqLogger.Error(err, "failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
79+
return reconcile.Result{}, err
80+
}
81+
}
82+
return reconcile.Result{}, err
83+
}
84+
85+
...
86+
}
87+
```
88+
89+
Log records will look like the following (from `reqLogger.Error()` above):
90+
91+
```
92+
2018-11-08T00:00:25.700Z ERROR operator-sdk.controller_memcached pkg/controller/memcached/memcached_controller.go:118 failed to create new Deployment {"Request.Namespace", "memcached", "Request.Name", "memcached-operator", "Deployment.Namespace", "memcached", "Deployment.Name", "memcached-operator"}
93+
```
94+
95+
## Non-default logging
96+
97+
If you do not want to use `logr` as your logging tool, you can remove `logr`-specific statements without issue from your operator's code, including the `logr` [setup code][code_set_logger] in `cmd/manager/main.go`, and add your own. Note that removing `logr` setup code will prevent `controller-runtime` from logging.
98+
99+
100+
[godoc_logr]:https://godoc.org/github.com/go-logr/logr
101+
[repo_zapr]:https://godoc.org/github.com/go-logr/zapr
102+
[godoc_logr_logger]:https://godoc.org/github.com/go-logr/logr#Logger
103+
[site_struct_logging]:https://www.client9.com/structured-logging-in-golang/
104+
[code_memcached_controller]:../../example/memcached-operator/memcached_controller.go.tmpl
105+
[code_set_logger]:https://github.com/operator-framework/operator-sdk/blob/948139171fff0e802c9e68f87cb95939941772ef/pkg/scaffold/cmd.go#L68-L72

example/memcached-operator/memcached_controller.go.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type ReconcileMemcached struct {
8787
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
8888
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
8989
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) {
90-
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Requst.Name", request.Name)
90+
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
9191
reqLogger.Info("Reconciling Memcached")
9292

9393
// Fetch the Memcached instance

0 commit comments

Comments
 (0)