Skip to content

Commit afb05e9

Browse files
authored
pkg/log/zap: adding helpers for zap logger configuration (#873)
* pkg/log/zap: adding helpers for zap logger configuration * pkg/scaffold/cmd.go: adding zap logger helper to scaffold * pkg/log/zap/flags.go: improve zap-devel help text, group sampleValue Set function with others * pkg/log/zap/flags.go: remove unnecessary import newlines * pkg/log/zap: remove factory; s/sink/syncer/g * test/*,commands/operator-sdk/cmd/up/local.go: include flags registered by imported packages * pkg/log/zap: bump copyright year to 2019 * pkg/scaffold: include flags registered by imported packages * pkg/(ansible|helm)/flags,commands,test: centralizing zap flagset setup Moves the zap flagset initialization into the ansible and helm flag packages so that they are reused in all ansible and helm operator scenarios (e.g. `up local`, `run`, and the helm binary) Also reverts parts a previous commit (975eddc) that added flags to the ansible and helm operators imported by other libraries because it caused the entire set of "testing" package flags to be added (from `operator-sdk` dependencies). * pkg/scaffold/,test/test-framework: whitespace and comment consistency * images/scorecard-proxy/cmd/proxy/main.go: use configurable zap logger * pkg/log/zap/flags.go: only allow debug, info, and error * images/scorecard-proxy/cmd/proxy/main.go: fix imports * doc/user/logging.md: document zap helpers and flags
1 parent d6b5a45 commit afb05e9

File tree

15 files changed

+307
-20
lines changed

15 files changed

+307
-20
lines changed

Gopkg.lock

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

commands/operator-sdk/cmd/run/ansible.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package run
1717
import (
1818
"github.com/operator-framework/operator-sdk/pkg/ansible"
1919
aoflags "github.com/operator-framework/operator-sdk/pkg/ansible/flags"
20+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
2021

2122
"github.com/spf13/cobra"
2223
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -32,7 +33,7 @@ func RunAnsibleCmd() *cobra.Command {
3233
in a Pod inside a cluster. Developers wanting to run their operator locally
3334
should use "up local" instead.`,
3435
RunE: func(cmd *cobra.Command, args []string) error {
35-
logf.SetLogger(logf.ZapLogger(false))
36+
logf.SetLogger(zap.Logger())
3637

3738
return ansible.Run(flags)
3839
},

commands/operator-sdk/cmd/run/helm.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package run
1717
import (
1818
"github.com/operator-framework/operator-sdk/pkg/helm"
1919
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"
20+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
2021

2122
"github.com/spf13/cobra"
2223
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -32,7 +33,7 @@ func RunHelmCmd() *cobra.Command {
3233
in a Pod inside a cluster. Developers wanting to run their operator locally
3334
should use "up local" instead.`,
3435
RunE: func(cmd *cobra.Command, args []string) error {
35-
logf.SetLogger(logf.ZapLogger(false))
36+
logf.SetLogger(zap.Logger())
3637

3738
return helm.Run(flags)
3839
},

commands/operator-sdk/cmd/up/local.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/operator-framework/operator-sdk/pkg/helm"
3232
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"
3333
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
34+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
3435
"github.com/operator-framework/operator-sdk/pkg/scaffold"
3536
sdkVersion "github.com/operator-framework/operator-sdk/version"
3637

@@ -136,15 +137,15 @@ func upLocal() error {
136137
}
137138

138139
func upLocalAnsible() error {
139-
logf.SetLogger(logf.ZapLogger(false))
140+
logf.SetLogger(zap.Logger())
140141
if err := setupOperatorEnv(); err != nil {
141142
return err
142143
}
143144
return ansible.Run(ansibleOperatorFlags)
144145
}
145146

146147
func upLocalHelm() error {
147-
logf.SetLogger(logf.ZapLogger(false))
148+
logf.SetLogger(zap.Logger())
148149
if err := setupOperatorEnv(); err != nil {
149150
return err
150151
}

doc/user/logging.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ Operators set the logger for all operator logging in [`cmd/manager/main.go`][cod
88

99
```Go
1010
import (
11+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
12+
"github.com/spf13/pflag"
1113
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
1214
)
1315

1416
func main() {
15-
logf.SetLogger(logf.ZapLogger(false))
17+
pflag.CommandLine.AddFlagSet(zap.FlagSet())
18+
pflag.Parse()
19+
logf.SetLogger(zap.Logger())
1620
log := logf.Log.WithName("cmd")
1721

1822
...
@@ -25,7 +29,19 @@ func main() {
2529

2630
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.
2731

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.
32+
### Default zap logger
33+
34+
Operator SDK uses a `zap`-based `logr` backend when scaffolding new projects. To assist with configuring and using this logger, the SDK includes several helper functions.
35+
36+
In the above example, we add the zap flagset to the operator's command line flags with `zap.FlagSet()`, and then set the controller-runtime logger with `zap.Logger()`.
37+
38+
By default, `zap.Logger()` will return a logger that is ready for production use. It uses a JSON encoder, logs starting at the `info` level, and has [sampling][zap_sampling] enabled. 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:
39+
* `--zap-devel` - Enables the zap development config (changes defaults to console encoder, debug log level, and disables sampling) (default: `false`)
40+
* `--zap-encoder` string - Sets the zap log encoding (`json` or `console`)
41+
* `--zap-level` string - Sets the zap log level (`debug`, `info`, or `error`)
42+
* `--zap-sample` - Enables zap's sampling mode
43+
44+
**NOTE:** Although the `logr` interface supports multiple debug levels (e.g. `log.V(1).Info()`, `log.V(2).Info()`, etc.), zap supports only a single debug level with `log.V(1).Info()`. Log statements with higher debug levels will not be printed with the zap's `logr` backend.
2945

3046
## Creating a structured log statement
3147

@@ -102,4 +118,5 @@ If you do not want to use `logr` as your logging tool, you can remove `logr`-spe
102118
[godoc_logr_logger]:https://godoc.org/github.com/go-logr/logr#Logger
103119
[site_struct_logging]:https://www.client9.com/structured-logging-in-golang/
104120
[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
121+
[code_set_logger]:https://github.com/operator-framework/operator-sdk/blob/ecd02000616f11303f1adecd3d4ceb4a8561a9ec/pkg/scaffold/cmd.go#L90-L94
122+
[zap_sampling]:https://github.com/uber-go/zap/blob/master/FAQ.md#why-sample-application-logs

images/scorecard-proxy/cmd/proxy/main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,25 @@
1515
package main
1616

1717
import (
18-
"flag"
1918
"os"
2019

2120
proxy "github.com/operator-framework/operator-sdk/pkg/ansible/proxy"
2221
"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap"
2322
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
23+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
2424

2525
log "github.com/sirupsen/logrus"
26+
"github.com/spf13/pflag"
2627
"sigs.k8s.io/controller-runtime/pkg/client/config"
2728
"sigs.k8s.io/controller-runtime/pkg/manager"
2829
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
2930
)
3031

3132
func main() {
32-
flag.Parse()
33-
logf.SetLogger(logf.ZapLogger(false))
33+
pflag.CommandLine.AddFlagSet(zap.FlagSet())
34+
pflag.Parse()
35+
36+
logf.SetLogger(zap.Logger())
3437

3538
namespace, found := os.LookupEnv(k8sutil.WatchNamespaceEnvVar)
3639
if found {

pkg/ansible/flags/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package flags
1616

1717
import (
1818
"github.com/operator-framework/operator-sdk/pkg/internal/flags"
19+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
1920
"github.com/spf13/pflag"
2021
)
2122

@@ -29,5 +30,6 @@ type AnsibleOperatorFlags struct {
2930
func AddTo(flagSet *pflag.FlagSet, helpTextPrefix ...string) *AnsibleOperatorFlags {
3031
aof := &AnsibleOperatorFlags{}
3132
aof.WatchFlags.AddTo(flagSet, helpTextPrefix...)
33+
flagSet.AddFlagSet(zap.FlagSet())
3234
return aof
3335
}

pkg/helm/flags/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package flags
1616

1717
import (
1818
"github.com/operator-framework/operator-sdk/pkg/internal/flags"
19+
"github.com/operator-framework/operator-sdk/pkg/log/zap"
1920
"github.com/spf13/pflag"
2021
)
2122

@@ -29,5 +30,6 @@ type HelmOperatorFlags struct {
2930
func AddTo(flagSet *pflag.FlagSet, helpTextPrefix ...string) *HelmOperatorFlags {
3031
hof := &HelmOperatorFlags{}
3132
hof.WatchFlags.AddTo(flagSet, helpTextPrefix...)
33+
flagSet.AddFlagSet(zap.FlagSet())
3234
return hof
3335
}

pkg/log/zap/flags.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package zap
16+
17+
import (
18+
"fmt"
19+
"strconv"
20+
"strings"
21+
22+
"github.com/spf13/pflag"
23+
"go.uber.org/zap"
24+
"go.uber.org/zap/zapcore"
25+
)
26+
27+
var (
28+
zapFlagSet *pflag.FlagSet
29+
30+
development bool
31+
encoderVal encoderValue
32+
levelVal levelValue
33+
sampleVal sampleValue
34+
)
35+
36+
func init() {
37+
zapFlagSet = pflag.NewFlagSet("zap", pflag.ExitOnError)
38+
zapFlagSet.BoolVar(&development, "zap-devel", false, "Enable zap development mode (changes defaults to console encoder, debug log level, and disables sampling)")
39+
zapFlagSet.Var(&encoderVal, "zap-encoder", "Zap log encoding ('json' or 'console')")
40+
zapFlagSet.Var(&levelVal, "zap-level", "Zap log level (one of 'debug', 'info', 'error')")
41+
zapFlagSet.Var(&sampleVal, "zap-sample", "Enable zap log sampling")
42+
}
43+
44+
func FlagSet() *pflag.FlagSet {
45+
return zapFlagSet
46+
}
47+
48+
type encoderValue struct {
49+
set bool
50+
encoder zapcore.Encoder
51+
str string
52+
}
53+
54+
func (v *encoderValue) Set(e string) error {
55+
v.set = true
56+
switch e {
57+
case "json":
58+
v.encoder = jsonEncoder()
59+
case "console":
60+
v.encoder = consoleEncoder()
61+
default:
62+
return fmt.Errorf("unknown encoder \"%s\"", e)
63+
}
64+
v.str = e
65+
return nil
66+
}
67+
68+
func (v encoderValue) String() string {
69+
return v.str
70+
}
71+
72+
func (v encoderValue) Type() string {
73+
return "encoder"
74+
}
75+
76+
func jsonEncoder() zapcore.Encoder {
77+
encoderConfig := zap.NewProductionEncoderConfig()
78+
return zapcore.NewJSONEncoder(encoderConfig)
79+
}
80+
81+
func consoleEncoder() zapcore.Encoder {
82+
encoderConfig := zap.NewDevelopmentEncoderConfig()
83+
return zapcore.NewConsoleEncoder(encoderConfig)
84+
}
85+
86+
type levelValue struct {
87+
set bool
88+
level zapcore.Level
89+
}
90+
91+
func (v *levelValue) Set(l string) error {
92+
v.set = true
93+
lower := strings.ToLower(l)
94+
switch lower {
95+
case "debug", "info", "error":
96+
return v.level.Set(l)
97+
}
98+
return fmt.Errorf("invalid log level \"%s\"", l)
99+
}
100+
101+
func (v levelValue) String() string {
102+
return v.level.String()
103+
}
104+
105+
func (v levelValue) Type() string {
106+
return "level"
107+
}
108+
109+
type sampleValue struct {
110+
set bool
111+
sample bool
112+
}
113+
114+
func (v *sampleValue) Set(s string) error {
115+
var err error
116+
v.set = true
117+
v.sample, err = strconv.ParseBool(s)
118+
return err
119+
}
120+
121+
func (v sampleValue) String() string {
122+
return strconv.FormatBool(v.sample)
123+
}
124+
125+
func (v sampleValue) IsBoolFlag() bool {
126+
return true
127+
}
128+
129+
func (v sampleValue) Type() string {
130+
return "sample"
131+
}

pkg/log/zap/logger.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package zap
16+
17+
import (
18+
"io"
19+
"os"
20+
"time"
21+
22+
"github.com/go-logr/logr"
23+
"github.com/go-logr/zapr"
24+
"go.uber.org/zap"
25+
"go.uber.org/zap/zapcore"
26+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
27+
)
28+
29+
func Logger() logr.Logger {
30+
return LoggerTo(os.Stderr)
31+
}
32+
33+
func LoggerTo(destWriter io.Writer) logr.Logger {
34+
syncer := zapcore.AddSync(destWriter)
35+
conf := getConfig(destWriter)
36+
37+
conf.encoder = &logf.KubeAwareEncoder{Encoder: conf.encoder, Verbose: conf.level.Level() < 0}
38+
if conf.sample {
39+
conf.opts = append(conf.opts, zap.WrapCore(func(core zapcore.Core) zapcore.Core {
40+
return zapcore.NewSampler(core, time.Second, 100, 100)
41+
}))
42+
}
43+
conf.opts = append(conf.opts, zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
44+
log := zap.New(zapcore.NewCore(conf.encoder, syncer, conf.level))
45+
log = log.WithOptions(conf.opts...)
46+
return zapr.NewLogger(log)
47+
}
48+
49+
type config struct {
50+
encoder zapcore.Encoder
51+
level zap.AtomicLevel
52+
sample bool
53+
opts []zap.Option
54+
}
55+
56+
func getConfig(destWriter io.Writer) config {
57+
var c config
58+
59+
// Set the defaults depending on the log mode (development vs. production)
60+
if development {
61+
c.encoder = consoleEncoder()
62+
c.level = zap.NewAtomicLevelAt(zap.DebugLevel)
63+
c.opts = append(c.opts, zap.Development(), zap.AddStacktrace(zap.ErrorLevel))
64+
c.sample = false
65+
} else {
66+
c.encoder = jsonEncoder()
67+
c.level = zap.NewAtomicLevelAt(zap.InfoLevel)
68+
c.opts = append(c.opts, zap.AddStacktrace(zap.WarnLevel))
69+
c.sample = true
70+
}
71+
72+
// Override the defaults if the flags were set explicitly on the command line
73+
if encoderVal.set {
74+
c.encoder = encoderVal.encoder
75+
}
76+
if levelVal.set {
77+
c.level = zap.NewAtomicLevelAt(levelVal.level)
78+
}
79+
if sampleVal.set {
80+
c.sample = sampleVal.sample
81+
}
82+
83+
return c
84+
}

0 commit comments

Comments
 (0)