...

Source file src/sigs.k8s.io/controller-runtime/pkg/log/zap/zap.go

Documentation: sigs.k8s.io/controller-runtime/pkg/log/zap

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package zap contains helpers for setting up a new logr.Logger instance
    18  // using the Zap logging framework.
    19  package zap
    20  
    21  import (
    22  	"flag"
    23  	"io"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/go-logr/logr"
    28  	"github.com/go-logr/zapr"
    29  	"go.uber.org/zap"
    30  	"go.uber.org/zap/zapcore"
    31  )
    32  
    33  // EncoderConfigOption is a function that can modify a `zapcore.EncoderConfig`.
    34  type EncoderConfigOption func(*zapcore.EncoderConfig)
    35  
    36  // NewEncoderFunc is a function that creates an Encoder using the provided EncoderConfigOptions.
    37  type NewEncoderFunc func(...EncoderConfigOption) zapcore.Encoder
    38  
    39  // New returns a brand new Logger configured with Opts. It
    40  // uses KubeAwareEncoder which adds Type information and
    41  // Namespace/Name to the log.
    42  func New(opts ...Opts) logr.Logger {
    43  	return zapr.NewLogger(NewRaw(opts...))
    44  }
    45  
    46  // Opts allows to manipulate Options.
    47  type Opts func(*Options)
    48  
    49  // UseDevMode sets the logger to use (or not use) development mode (more
    50  // human-readable output, extra stack traces and logging information, etc).
    51  // See Options.Development.
    52  func UseDevMode(enabled bool) Opts {
    53  	return func(o *Options) {
    54  		o.Development = enabled
    55  	}
    56  }
    57  
    58  // WriteTo configures the logger to write to the given io.Writer, instead of standard error.
    59  // See Options.DestWriter.
    60  func WriteTo(out io.Writer) Opts {
    61  	return func(o *Options) {
    62  		o.DestWriter = out
    63  	}
    64  }
    65  
    66  // Encoder configures how the logger will encode the output e.g JSON or console.
    67  // See Options.Encoder.
    68  func Encoder(encoder zapcore.Encoder) func(o *Options) {
    69  	return func(o *Options) {
    70  		o.Encoder = encoder
    71  	}
    72  }
    73  
    74  // JSONEncoder configures the logger to use a JSON Encoder.
    75  func JSONEncoder(opts ...EncoderConfigOption) func(o *Options) {
    76  	return func(o *Options) {
    77  		o.Encoder = newJSONEncoder(opts...)
    78  	}
    79  }
    80  
    81  func newJSONEncoder(opts ...EncoderConfigOption) zapcore.Encoder {
    82  	encoderConfig := zap.NewProductionEncoderConfig()
    83  	for _, opt := range opts {
    84  		opt(&encoderConfig)
    85  	}
    86  	return zapcore.NewJSONEncoder(encoderConfig)
    87  }
    88  
    89  // ConsoleEncoder configures the logger to use a Console encoder.
    90  func ConsoleEncoder(opts ...EncoderConfigOption) func(o *Options) {
    91  	return func(o *Options) {
    92  		o.Encoder = newConsoleEncoder(opts...)
    93  	}
    94  }
    95  
    96  func newConsoleEncoder(opts ...EncoderConfigOption) zapcore.Encoder {
    97  	encoderConfig := zap.NewDevelopmentEncoderConfig()
    98  	for _, opt := range opts {
    99  		opt(&encoderConfig)
   100  	}
   101  	return zapcore.NewConsoleEncoder(encoderConfig)
   102  }
   103  
   104  // Level sets Options.Level, which configures the minimum enabled logging level e.g Debug, Info.
   105  // A zap log level should be multiplied by -1 to get the logr verbosity.
   106  // For example, to get logr verbosity of 3, pass zapcore.Level(-3) to this Opts.
   107  // See https://pkg.go.dev/github.com/go-logr/zapr for how zap level relates to logr verbosity.
   108  func Level(level zapcore.LevelEnabler) func(o *Options) {
   109  	return func(o *Options) {
   110  		o.Level = level
   111  	}
   112  }
   113  
   114  // StacktraceLevel sets Options.StacktraceLevel, which configures the logger to record a stack trace
   115  // for all messages at or above a given level.
   116  // See the Level Opts for the relationship of zap log level to logr verbosity.
   117  func StacktraceLevel(stacktraceLevel zapcore.LevelEnabler) func(o *Options) {
   118  	return func(o *Options) {
   119  		o.StacktraceLevel = stacktraceLevel
   120  	}
   121  }
   122  
   123  // RawZapOpts allows appending arbitrary zap.Options to configure the underlying zap logger.
   124  // See Options.ZapOpts.
   125  func RawZapOpts(zapOpts ...zap.Option) func(o *Options) {
   126  	return func(o *Options) {
   127  		o.ZapOpts = append(o.ZapOpts, zapOpts...)
   128  	}
   129  }
   130  
   131  // Options contains all possible settings.
   132  type Options struct {
   133  	// Development configures the logger to use a Zap development config
   134  	// (stacktraces on warnings, no sampling), otherwise a Zap production
   135  	// config will be used (stacktraces on errors, sampling).
   136  	Development bool
   137  	// Encoder configures how Zap will encode the output.  Defaults to
   138  	// console when Development is true and JSON otherwise
   139  	Encoder zapcore.Encoder
   140  	// EncoderConfigOptions can modify the EncoderConfig needed to initialize an Encoder.
   141  	// See https://pkg.go.dev/go.uber.org/zap/zapcore#EncoderConfig for the list of options
   142  	// that can be configured.
   143  	// Note that the EncoderConfigOptions are not applied when the Encoder option is already set.
   144  	EncoderConfigOptions []EncoderConfigOption
   145  	// NewEncoder configures Encoder using the provided EncoderConfigOptions.
   146  	// Note that the NewEncoder function is not used when the Encoder option is already set.
   147  	NewEncoder NewEncoderFunc
   148  	// DestWriter controls the destination of the log output.  Defaults to
   149  	// os.Stderr.
   150  	DestWriter io.Writer
   151  	// Level configures the verbosity of the logging.
   152  	// Defaults to Debug when Development is true and Info otherwise.
   153  	// A zap log level should be multiplied by -1 to get the logr verbosity.
   154  	// For example, to get logr verbosity of 3, set this field to zapcore.Level(-3).
   155  	// See https://pkg.go.dev/github.com/go-logr/zapr for how zap level relates to logr verbosity.
   156  	Level zapcore.LevelEnabler
   157  	// StacktraceLevel is the level at and above which stacktraces will
   158  	// be recorded for all messages. Defaults to Warn when Development
   159  	// is true and Error otherwise.
   160  	// See Level for the relationship of zap log level to logr verbosity.
   161  	StacktraceLevel zapcore.LevelEnabler
   162  	// ZapOpts allows passing arbitrary zap.Options to configure on the
   163  	// underlying Zap logger.
   164  	ZapOpts []zap.Option
   165  	// TimeEncoder specifies the encoder for the timestamps in log messages.
   166  	// Defaults to RFC3339TimeEncoder.
   167  	TimeEncoder zapcore.TimeEncoder
   168  }
   169  
   170  // addDefaults adds defaults to the Options.
   171  func (o *Options) addDefaults() {
   172  	if o.DestWriter == nil {
   173  		o.DestWriter = os.Stderr
   174  	}
   175  
   176  	if o.Development {
   177  		if o.NewEncoder == nil {
   178  			o.NewEncoder = newConsoleEncoder
   179  		}
   180  		if o.Level == nil {
   181  			lvl := zap.NewAtomicLevelAt(zap.DebugLevel)
   182  			o.Level = &lvl
   183  		}
   184  		if o.StacktraceLevel == nil {
   185  			lvl := zap.NewAtomicLevelAt(zap.WarnLevel)
   186  			o.StacktraceLevel = &lvl
   187  		}
   188  		o.ZapOpts = append(o.ZapOpts, zap.Development())
   189  	} else {
   190  		if o.NewEncoder == nil {
   191  			o.NewEncoder = newJSONEncoder
   192  		}
   193  		if o.Level == nil {
   194  			lvl := zap.NewAtomicLevelAt(zap.InfoLevel)
   195  			o.Level = &lvl
   196  		}
   197  		if o.StacktraceLevel == nil {
   198  			lvl := zap.NewAtomicLevelAt(zap.ErrorLevel)
   199  			o.StacktraceLevel = &lvl
   200  		}
   201  		// Disable sampling for increased Debug levels. Otherwise, this will
   202  		// cause index out of bounds errors in the sampling code.
   203  		if !o.Level.Enabled(zapcore.Level(-2)) {
   204  			o.ZapOpts = append(o.ZapOpts,
   205  				zap.WrapCore(func(core zapcore.Core) zapcore.Core {
   206  					return zapcore.NewSamplerWithOptions(core, time.Second, 100, 100)
   207  				}))
   208  		}
   209  	}
   210  
   211  	if o.TimeEncoder == nil {
   212  		o.TimeEncoder = zapcore.RFC3339TimeEncoder
   213  	}
   214  	f := func(ecfg *zapcore.EncoderConfig) {
   215  		ecfg.EncodeTime = o.TimeEncoder
   216  	}
   217  	// prepend instead of append it in case someone adds a time encoder option in it
   218  	o.EncoderConfigOptions = append([]EncoderConfigOption{f}, o.EncoderConfigOptions...)
   219  
   220  	if o.Encoder == nil {
   221  		o.Encoder = o.NewEncoder(o.EncoderConfigOptions...)
   222  	}
   223  	o.ZapOpts = append(o.ZapOpts, zap.AddStacktrace(o.StacktraceLevel))
   224  }
   225  
   226  // NewRaw returns a new zap.Logger configured with the passed Opts
   227  // or their defaults. It uses KubeAwareEncoder which adds Type
   228  // information and Namespace/Name to the log.
   229  func NewRaw(opts ...Opts) *zap.Logger {
   230  	o := &Options{}
   231  	for _, opt := range opts {
   232  		opt(o)
   233  	}
   234  	o.addDefaults()
   235  
   236  	// this basically mimics New<type>Config, but with a custom sink
   237  	sink := zapcore.AddSync(o.DestWriter)
   238  
   239  	o.ZapOpts = append(o.ZapOpts, zap.ErrorOutput(sink))
   240  	log := zap.New(zapcore.NewCore(&KubeAwareEncoder{Encoder: o.Encoder, Verbose: o.Development}, sink, o.Level))
   241  	log = log.WithOptions(o.ZapOpts...)
   242  	return log
   243  }
   244  
   245  // BindFlags will parse the given flagset for zap option flags and set the log options accordingly:
   246  //   - zap-devel:
   247  //     Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn)
   248  //     Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)
   249  //   - zap-encoder: Zap log encoding (one of 'json' or 'console')
   250  //   - zap-log-level: Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error',
   251  //     or any integer value > 0 which corresponds to custom debug levels of increasing verbosity").
   252  //   - zap-stacktrace-level: Zap Level at and above which stacktraces are captured (one of 'info', 'error' or 'panic')
   253  //   - zap-time-encoding: Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano'),
   254  //     Defaults to 'epoch'.
   255  func (o *Options) BindFlags(fs *flag.FlagSet) {
   256  	// Set Development mode value
   257  	fs.BoolVar(&o.Development, "zap-devel", o.Development,
   258  		"Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). "+
   259  			"Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)")
   260  
   261  	// Set Encoder value
   262  	var encVal encoderFlag
   263  	encVal.setFunc = func(fromFlag NewEncoderFunc) {
   264  		o.NewEncoder = fromFlag
   265  	}
   266  	fs.Var(&encVal, "zap-encoder", "Zap log encoding (one of 'json' or 'console')")
   267  
   268  	// Set the Log Level
   269  	var levelVal levelFlag
   270  	levelVal.setFunc = func(fromFlag zapcore.LevelEnabler) {
   271  		o.Level = fromFlag
   272  	}
   273  	fs.Var(&levelVal, "zap-log-level",
   274  		"Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', "+
   275  			"or any integer value > 0 which corresponds to custom debug levels of increasing verbosity")
   276  
   277  	// Set the StrackTrace Level
   278  	var stackVal stackTraceFlag
   279  	stackVal.setFunc = func(fromFlag zapcore.LevelEnabler) {
   280  		o.StacktraceLevel = fromFlag
   281  	}
   282  	fs.Var(&stackVal, "zap-stacktrace-level",
   283  		"Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic').")
   284  
   285  	// Set the time encoding
   286  	var timeEncoderVal timeEncodingFlag
   287  	timeEncoderVal.setFunc = func(fromFlag zapcore.TimeEncoder) {
   288  		o.TimeEncoder = fromFlag
   289  	}
   290  	fs.Var(&timeEncoderVal, "zap-time-encoding", "Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano'). Defaults to 'epoch'.")
   291  }
   292  
   293  // UseFlagOptions configures the logger to use the Options set by parsing zap option flags from the CLI.
   294  //
   295  //	opts := zap.Options{}
   296  //	opts.BindFlags(flag.CommandLine)
   297  //	flag.Parse()
   298  //	log := zap.New(zap.UseFlagOptions(&opts))
   299  func UseFlagOptions(in *Options) Opts {
   300  	return func(o *Options) {
   301  		*o = *in
   302  	}
   303  }
   304  

View as plain text