From 1fbf8f3d89dc6ed22e5a85cc55039206a1a2936f Mon Sep 17 00:00:00 2001 From: Grzegorz Piotrowski Date: Wed, 8 Nov 2023 13:45:50 +0000 Subject: [PATCH] Setup Authorino logger --- go.mod | 5 +- go.sum | 2 + main.go | 28 +++++++-- pkg/log/logger.go | 129 +++++++++++++++++++++++++++++++++++++++++ pkg/log/logger_test.go | 61 +++++++++++++++++++ 5 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 pkg/log/logger.go create mode 100644 pkg/log/logger_test.go diff --git a/go.mod b/go.mod index da0238eb..cdbad25b 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,12 @@ require ( github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 + go.uber.org/zap v1.25.0 + gotest.tools v2.2.0+incompatible k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 + k8s.io/klog/v2 v2.100.1 sigs.k8s.io/controller-runtime v0.16.3 ) @@ -47,7 +50,6 @@ require ( github.com/prometheus/procfs v0.10.1 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect @@ -64,7 +66,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.28.3 // indirect k8s.io/component-base v0.28.3 // indirect - k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index 3bd362ce..6c73e5b0 100644 --- a/go.sum +++ b/go.sum @@ -201,6 +201,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= diff --git a/main.go b/main.go index e934c13d..54946103 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/kuadrant/authorino-operator/pkg/log" + authorinooperatorv1beta1 "github.com/kuadrant/authorino-operator/api/v1beta1" "github.com/kuadrant/authorino-operator/controllers" //+kubebuilder:scaffold:imports @@ -41,8 +43,8 @@ import ( var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") - - version string // value injected in compilation-time + logger log.Logger + version string // value injected in compilation-time ) func init() { @@ -52,24 +54,42 @@ func init() { //+kubebuilder:scaffold:scheme } +type logOptions struct { + level string + mode string +} + +func setupLogger(opts logOptions) { + logOpts := log.Options{Level: log.ToLogLevel(opts.level), Mode: log.ToLogMode(opts.mode)} + logger = log.NewLogger(logOpts).WithName("authorino-operator").WithName("controller").WithName("Authorino") + log.SetLogger(logger) +} + func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string + logOpts := logOptions{} + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + flag.StringVar(&logOpts.level, "log-level", "info", "Log level (info, debug, error, etc.)") + flag.StringVar(&logOpts.mode, "log-mode", "production", "Log mode (development or production)") + opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() + setupLogger(logOpts) + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - setupLog.Info("botting up authorino operator", "version", version, "default authorino image", controllers.DefaultAuthorinoImage) + setupLog.Info("booting up authorino operator", "version", version, "default authorino image", controllers.DefaultAuthorinoImage) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -86,7 +106,7 @@ func main() { if err = (&controllers.AuthorinoReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("authorino-operator").WithName("controller").WithName("Authorino"), + Log: logger, Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Authorino") diff --git a/pkg/log/logger.go b/pkg/log/logger.go new file mode 100644 index 00000000..766dc23c --- /dev/null +++ b/pkg/log/logger.go @@ -0,0 +1,129 @@ +package log + +import ( + "context" + "strings" + + "github.com/go-logr/logr" + "go.uber.org/zap/zapcore" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + // Log is a singleton base logger that can be used across the system, + // either directly or to create other loggers with name, with values, + // and/or locked to a given log level. + // It is initialized to the promise delegation log provided by + // sigs.k8s.io/controller-runtime, which points to a no-op (null) logger + // until `SetLogger` is called. + // This is also useful for mocking the default logger tests. + Log Logger = ctrl.Log +) + +type Logger = logr.Logger + +type LogLevel zapcore.Level + +func (l *LogLevel) String() string { + return zapcore.Level(*l).String() +} + +// ToLogLevel converts a string to a log level. +func ToLogLevel(level string) LogLevel { + var l zapcore.Level + _ = l.UnmarshalText([]byte(level)) + return LogLevel(l) +} + +// LogMode defines the log output mode. +type LogMode int8 + +const ( + // LogModeProd is the log mode for production. + LogModeProd LogMode = iota + // LogModeDev is for more human-readable outputs, extra stack traces + // and logging info. (aka Zap's "development config".) + LogModeDev +) + +func (f *LogMode) String() string { + switch *f { + case LogModeProd: + return "production" + case LogModeDev: + return "development" + default: + return "unknown" + } +} + +// ToLogMode converts a string to a log mode. +// Use either 'production' for `LogModeProd` or 'development' for `LogModeDev`. +func ToLogMode(mode string) LogMode { + switch strings.ToLower(mode) { + case "production": + return LogModeProd + case "development": + return LogModeDev + default: + panic("unknown log mode") + } +} + +// Options is a set of options for a configured logger. +type Options struct { + Level LogLevel + Mode LogMode +} + +// SetLogger sets up a logger. +func SetLogger(logger Logger) { + Log = logger + + ctrl.SetLogger(Log) // fulfills `logger` as the de facto logger used by controller-runtime + klog.SetLogger(Log) +} + +// WithName uses the singleton logger to create a new logger with the given name. +func WithName(name string) Logger { + return Log.WithName(name) +} + +// WithName uses the singleton logger to create a new logger with the given values. +func WithValues(keysAndValues ...interface{}) Logger { + return Log.WithValues(keysAndValues...) +} + +// V uses the singleton logger to create a new logger for the given log level. +func V(level int) Logger { + return Log.V(level) +} + +// IntoContext takes a context and sets the logger as one of its values. +// Use FromContext function to retrieve the logger. +func IntoContext(ctx context.Context, log Logger) context.Context { + return logr.NewContext(ctx, log) +} + +// FromContext returns a logger with predefined values from a context.Context. +func FromContext(ctx context.Context, keysAndValues ...interface{}) Logger { + var l logr.Logger = Log + if ctx != nil { + if logger, err := logr.FromContext(ctx); err == nil { + l = logger + } + } + return l.WithValues(keysAndValues...) +} + +// NewLogger returns a new logger with the given options. +// `logger` param is the actual logger implementation; when omitted, a new +// logger based on sigs.k8s.io/controller-runtime/pkg/log/zap is created. +func NewLogger(opts Options) Logger { + return zap.New( + zap.Level(zapcore.Level(opts.Level)), + zap.UseDevMode(opts.Mode == LogModeDev), + ) +} diff --git a/pkg/log/logger_test.go b/pkg/log/logger_test.go new file mode 100644 index 00000000..ee6ea446 --- /dev/null +++ b/pkg/log/logger_test.go @@ -0,0 +1,61 @@ +package log + +import ( + "testing" + + "gotest.tools/assert" +) + +func TestLogLevelToString(t *testing.T) { + level := LogLevel(-1) + assert.Equal(t, level.String(), "debug") + + level = LogLevel(0) + assert.Equal(t, level.String(), "info") + + level = LogLevel(1) + assert.Equal(t, level.String(), "warn") + + level = LogLevel(2) + assert.Equal(t, level.String(), "error") + + level = LogLevel(3) + assert.Equal(t, level.String(), "dpanic") + + level = LogLevel(4) + assert.Equal(t, level.String(), "panic") + + level = LogLevel(5) + assert.Equal(t, level.String(), "fatal") +} + +func TestToLogLevel(t *testing.T) { + assert.Equal(t, int(ToLogLevel("debug")), -1) + assert.Equal(t, int(ToLogLevel("info")), 0) + assert.Equal(t, int(ToLogLevel("warn")), 1) + assert.Equal(t, int(ToLogLevel("error")), 2) + assert.Equal(t, int(ToLogLevel("dpanic")), 3) + assert.Equal(t, int(ToLogLevel("panic")), 4) + assert.Equal(t, int(ToLogLevel("fatal")), 5) + assert.Equal(t, int(ToLogLevel("invalid")), 0) // falls back to default log level (info) without panicking +} + +func TestLogModeToString(t *testing.T) { + level := LogMode(0) + assert.Equal(t, level.String(), "production") + + level = LogMode(1) + assert.Equal(t, level.String(), "development") +} + +func TestToLogMode(t *testing.T) { + assert.Equal(t, int(ToLogMode("production")), 0) + assert.Equal(t, int(ToLogMode("development")), 1) + + defer func() { + if r := recover(); r == nil { + t.Errorf(`ToLogMode("invalid") was expected to panic and it did not.`) + } + }() + _ = ToLogMode("invalid") +}