Logging
Overview
Operator SDK uses a -based logr
backend when scaffolding new projects. To assist with configuring and using this logger, the SDK includes several helper functions.
In the simple example below, we add the zap flagset to the operator’s command line flags with BindFlags()
, and then set the controller-runtime logger with zap.Options{}
.
By default, zap.Options{}
will return a logger that is ready for production use. It uses a JSON encoder, logs starting at the info
level. To customize the default behavior, users can use the zap flagset and specify flags on the command line. The zap flagset includes the following flags that can be used to configure the logger:
--zap-devel
: Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn) Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)--zap-encoder
: Zap log encoding (‘json’ or ‘console’)--zap-log-level
: Zap Level to configure the verbosity of logging. Can be one of ‘debug’, ‘info’, ‘error’, or any integer value > 0 which corresponds to custom debug levels of increasing verbosity”)--zap-stacktrace-level
: Zap Level at and above which stacktraces are captured (one of ‘info’ or ‘error’)
Consult the controller-runtime for more detailed flag information.
Operators set the logger for all operator logging in main.go
. To illustrate how this works, try out this simple example:
Output using the defaults
$ go run main.go
INFO[0000] Running the operator locally in namespace default.
{"level":"info","ts":1587741740.407766,"logger":"global","msg":"Printing at INFO level"}
{"level":"info","ts":1587741740.407855,"logger":"scoped","msg":"Printing at INFO level"}
Output overriding the log level to 1 (debug)
$ go run main.go --zap-log-level=debug
INFO[0000] Running the operator locally in namespace default.
{"level":"info","ts":1587741837.602911,"logger":"global","msg":"Printing at INFO level"}
{"level":"debug","ts":1587741837.602964,"logger":"global","msg":"Printing at DEBUG level"}
{"level":"info","ts":1587741837.6029708,"logger":"scoped","msg":"Printing at INFO level"}
Below is an example illustrating the use of zap-logfmt in logging.
In your main.go
file, replace the current implementation for logs inside the main
function:
With:
import(
...
zaplogfmt "github.com/sykesm/zap-logfmt"
uzap "go.uber.org/zap"
"go.uber.org/zap/zapcore"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
...
)
configLog := uzap.NewProductionEncoderConfig()
configLog.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
}
logfmtEncoder := zaplogfmt.NewEncoder(configLog)
// Construct a new logr.logger.
logger := zap.New(zap.UseDevMode(true), zap.WriteTo(os.Stdout), zap.Encoder(logfmtEncoder))
logf.SetLogger(logger)
NOTE: For this example, you will need to add the module "github.com/sykesm/zap-logfmt"
to your project. Run go get -u github.com/sykesm/zap-logfmt
.
Output using custom zap logger
$ go run main.go
ts=2020-04-30T20:35:59.551268Z level=info logger=global msg="Printing at INFO level"
ts=2020-04-30T20:35:59.551314Z level=debug logger=global msg="Printing at DEBUG level"
ts=2020-04-30T20:35:59.551318Z level=info logger=scoped msg="Printing at INFO level"
ts=2020-04-30T20:35:59.55132Z level=debug logger=scoped msg="Printing at DEBUG level"
By using sigs.k8s.io/controller-runtime/pkg/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.
Make sure to have your run
target to take ARGS
as shown below in Makefile
.
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet manifests
When deploying your operator to a cluster you can set additional flags using an args
array in your operator’s container
spec in the file config/default/manager_auth_proxy_patch.yaml
For example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
containers:
- name: kube-rbac-proxy
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8080/"
- "--logtostderr=true"
- "--v=10"
ports:
- containerPort: 8443
name: https
- name: manager
args:
- "--metrics-addr=127.0.0.1:8080"
- "--enable-leader-election"
- "--zap-encoder=console"
- "--zap-log-level=debug"
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
.
An example from memcached_controller.go:
Log records will look like the following (from log.Error()
above):