config.go raw

   1  // Copyright (c) 2016 Uber Technologies, Inc.
   2  //
   3  // Permission is hereby granted, free of charge, to any person obtaining a copy
   4  // of this software and associated documentation files (the "Software"), to deal
   5  // in the Software without restriction, including without limitation the rights
   6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   7  // copies of the Software, and to permit persons to whom the Software is
   8  // furnished to do so, subject to the following conditions:
   9  //
  10  // The above copyright notice and this permission notice shall be included in
  11  // all copies or substantial portions of the Software.
  12  //
  13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19  // THE SOFTWARE.
  20  
  21  package zap
  22  
  23  import (
  24  	"errors"
  25  	"sort"
  26  	"time"
  27  
  28  	"go.uber.org/zap/zapcore"
  29  )
  30  
  31  // SamplingConfig sets a sampling strategy for the logger. Sampling caps the
  32  // global CPU and I/O load that logging puts on your process while attempting
  33  // to preserve a representative subset of your logs.
  34  //
  35  // If specified, the Sampler will invoke the Hook after each decision.
  36  //
  37  // Values configured here are per-second. See zapcore.NewSamplerWithOptions for
  38  // details.
  39  type SamplingConfig struct {
  40  	Initial    int                                           `json:"initial" yaml:"initial"`
  41  	Thereafter int                                           `json:"thereafter" yaml:"thereafter"`
  42  	Hook       func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"`
  43  }
  44  
  45  // Config offers a declarative way to construct a logger. It doesn't do
  46  // anything that can't be done with New, Options, and the various
  47  // zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
  48  // toggle common options.
  49  //
  50  // Note that Config intentionally supports only the most common options. More
  51  // unusual logging setups (logging to network connections or message queues,
  52  // splitting output between multiple files, etc.) are possible, but require
  53  // direct use of the zapcore package. For sample code, see the package-level
  54  // BasicConfiguration and AdvancedConfiguration examples.
  55  //
  56  // For an example showing runtime log level changes, see the documentation for
  57  // AtomicLevel.
  58  type Config struct {
  59  	// Level is the minimum enabled logging level. Note that this is a dynamic
  60  	// level, so calling Config.Level.SetLevel will atomically change the log
  61  	// level of all loggers descended from this config.
  62  	Level AtomicLevel `json:"level" yaml:"level"`
  63  	// Development puts the logger in development mode, which changes the
  64  	// behavior of DPanicLevel and takes stacktraces more liberally.
  65  	Development bool `json:"development" yaml:"development"`
  66  	// DisableCaller stops annotating logs with the calling function's file
  67  	// name and line number. By default, all logs are annotated.
  68  	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
  69  	// DisableStacktrace completely disables automatic stacktrace capturing. By
  70  	// default, stacktraces are captured for WarnLevel and above logs in
  71  	// development and ErrorLevel and above in production.
  72  	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
  73  	// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
  74  	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
  75  	// Encoding sets the logger's encoding. Valid values are "json" and
  76  	// "console", as well as any third-party encodings registered via
  77  	// RegisterEncoder.
  78  	Encoding string `json:"encoding" yaml:"encoding"`
  79  	// EncoderConfig sets options for the chosen encoder. See
  80  	// zapcore.EncoderConfig for details.
  81  	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
  82  	// OutputPaths is a list of URLs or file paths to write logging output to.
  83  	// See Open for details.
  84  	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
  85  	// ErrorOutputPaths is a list of URLs to write internal logger errors to.
  86  	// The default is standard error.
  87  	//
  88  	// Note that this setting only affects internal errors; for sample code that
  89  	// sends error-level logs to a different location from info- and debug-level
  90  	// logs, see the package-level AdvancedConfiguration example.
  91  	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
  92  	// InitialFields is a collection of fields to add to the root logger.
  93  	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
  94  }
  95  
  96  // NewProductionEncoderConfig returns an opinionated EncoderConfig for
  97  // production environments.
  98  //
  99  // Messages encoded with this configuration will be JSON-formatted
 100  // and will have the following keys by default:
 101  //
 102  //   - "level": The logging level (e.g. "info", "error").
 103  //   - "ts": The current time in number of seconds since the Unix epoch.
 104  //   - "msg": The message passed to the log statement.
 105  //   - "caller": If available, a short path to the file and line number
 106  //     where the log statement was issued.
 107  //     The logger configuration determines whether this field is captured.
 108  //   - "stacktrace": If available, a stack trace from the line
 109  //     where the log statement was issued.
 110  //     The logger configuration determines whether this field is captured.
 111  //
 112  // By default, the following formats are used for different types:
 113  //
 114  //   - Time is formatted as floating-point number of seconds since the Unix
 115  //     epoch.
 116  //   - Duration is formatted as floating-point number of seconds.
 117  //
 118  // You may change these by setting the appropriate fields in the returned
 119  // object.
 120  // For example, use the following to change the time encoding format:
 121  //
 122  //	cfg := zap.NewProductionEncoderConfig()
 123  //	cfg.EncodeTime = zapcore.ISO8601TimeEncoder
 124  func NewProductionEncoderConfig() zapcore.EncoderConfig {
 125  	return zapcore.EncoderConfig{
 126  		TimeKey:        "ts",
 127  		LevelKey:       "level",
 128  		NameKey:        "logger",
 129  		CallerKey:      "caller",
 130  		FunctionKey:    zapcore.OmitKey,
 131  		MessageKey:     "msg",
 132  		StacktraceKey:  "stacktrace",
 133  		LineEnding:     zapcore.DefaultLineEnding,
 134  		EncodeLevel:    zapcore.LowercaseLevelEncoder,
 135  		EncodeTime:     zapcore.EpochTimeEncoder,
 136  		EncodeDuration: zapcore.SecondsDurationEncoder,
 137  		EncodeCaller:   zapcore.ShortCallerEncoder,
 138  	}
 139  }
 140  
 141  // NewProductionConfig builds a reasonable default production logging
 142  // configuration.
 143  // Logging is enabled at InfoLevel and above, and uses a JSON encoder.
 144  // Logs are written to standard error.
 145  // Stacktraces are included on logs of ErrorLevel and above.
 146  // DPanicLevel logs will not panic, but will write a stacktrace.
 147  //
 148  // Sampling is enabled at 100:100 by default,
 149  // meaning that after the first 100 log entries
 150  // with the same level and message in the same second,
 151  // it will log every 100th entry
 152  // with the same level and message in the same second.
 153  // You may disable this behavior by setting Sampling to nil.
 154  //
 155  // See [NewProductionEncoderConfig] for information
 156  // on the default encoder configuration.
 157  func NewProductionConfig() Config {
 158  	return Config{
 159  		Level:       NewAtomicLevelAt(InfoLevel),
 160  		Development: false,
 161  		Sampling: &SamplingConfig{
 162  			Initial:    100,
 163  			Thereafter: 100,
 164  		},
 165  		Encoding:         "json",
 166  		EncoderConfig:    NewProductionEncoderConfig(),
 167  		OutputPaths:      []string{"stderr"},
 168  		ErrorOutputPaths: []string{"stderr"},
 169  	}
 170  }
 171  
 172  // NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
 173  // development environments.
 174  //
 175  // Messages encoded with this configuration will use Zap's console encoder
 176  // intended to print human-readable output.
 177  // It will print log messages with the following information:
 178  //
 179  //   - The log level (e.g. "INFO", "ERROR").
 180  //   - The time in ISO8601 format (e.g. "2017-01-01T12:00:00Z").
 181  //   - The message passed to the log statement.
 182  //   - If available, a short path to the file and line number
 183  //     where the log statement was issued.
 184  //     The logger configuration determines whether this field is captured.
 185  //   - If available, a stacktrace from the line
 186  //     where the log statement was issued.
 187  //     The logger configuration determines whether this field is captured.
 188  //
 189  // By default, the following formats are used for different types:
 190  //
 191  //   - Time is formatted in ISO8601 format (e.g. "2017-01-01T12:00:00Z").
 192  //   - Duration is formatted as a string (e.g. "1.234s").
 193  //
 194  // You may change these by setting the appropriate fields in the returned
 195  // object.
 196  // For example, use the following to change the time encoding format:
 197  //
 198  //	cfg := zap.NewDevelopmentEncoderConfig()
 199  //	cfg.EncodeTime = zapcore.ISO8601TimeEncoder
 200  func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
 201  	return zapcore.EncoderConfig{
 202  		// Keys can be anything except the empty string.
 203  		TimeKey:        "T",
 204  		LevelKey:       "L",
 205  		NameKey:        "N",
 206  		CallerKey:      "C",
 207  		FunctionKey:    zapcore.OmitKey,
 208  		MessageKey:     "M",
 209  		StacktraceKey:  "S",
 210  		LineEnding:     zapcore.DefaultLineEnding,
 211  		EncodeLevel:    zapcore.CapitalLevelEncoder,
 212  		EncodeTime:     zapcore.ISO8601TimeEncoder,
 213  		EncodeDuration: zapcore.StringDurationEncoder,
 214  		EncodeCaller:   zapcore.ShortCallerEncoder,
 215  	}
 216  }
 217  
 218  // NewDevelopmentConfig builds a reasonable default development logging
 219  // configuration.
 220  // Logging is enabled at DebugLevel and above, and uses a console encoder.
 221  // Logs are written to standard error.
 222  // Stacktraces are included on logs of WarnLevel and above.
 223  // DPanicLevel logs will panic.
 224  //
 225  // See [NewDevelopmentEncoderConfig] for information
 226  // on the default encoder configuration.
 227  func NewDevelopmentConfig() Config {
 228  	return Config{
 229  		Level:            NewAtomicLevelAt(DebugLevel),
 230  		Development:      true,
 231  		Encoding:         "console",
 232  		EncoderConfig:    NewDevelopmentEncoderConfig(),
 233  		OutputPaths:      []string{"stderr"},
 234  		ErrorOutputPaths: []string{"stderr"},
 235  	}
 236  }
 237  
 238  // Build constructs a logger from the Config and Options.
 239  func (cfg Config) Build(opts ...Option) (*Logger, error) {
 240  	enc, err := cfg.buildEncoder()
 241  	if err != nil {
 242  		return nil, err
 243  	}
 244  
 245  	sink, errSink, err := cfg.openSinks()
 246  	if err != nil {
 247  		return nil, err
 248  	}
 249  
 250  	if cfg.Level == (AtomicLevel{}) {
 251  		return nil, errors.New("missing Level")
 252  	}
 253  
 254  	log := New(
 255  		zapcore.NewCore(enc, sink, cfg.Level),
 256  		cfg.buildOptions(errSink)...,
 257  	)
 258  	if len(opts) > 0 {
 259  		log = log.WithOptions(opts...)
 260  	}
 261  	return log, nil
 262  }
 263  
 264  func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
 265  	opts := []Option{ErrorOutput(errSink)}
 266  
 267  	if cfg.Development {
 268  		opts = append(opts, Development())
 269  	}
 270  
 271  	if !cfg.DisableCaller {
 272  		opts = append(opts, AddCaller())
 273  	}
 274  
 275  	stackLevel := ErrorLevel
 276  	if cfg.Development {
 277  		stackLevel = WarnLevel
 278  	}
 279  	if !cfg.DisableStacktrace {
 280  		opts = append(opts, AddStacktrace(stackLevel))
 281  	}
 282  
 283  	if scfg := cfg.Sampling; scfg != nil {
 284  		opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
 285  			var samplerOpts []zapcore.SamplerOption
 286  			if scfg.Hook != nil {
 287  				samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook))
 288  			}
 289  			return zapcore.NewSamplerWithOptions(
 290  				core,
 291  				time.Second,
 292  				cfg.Sampling.Initial,
 293  				cfg.Sampling.Thereafter,
 294  				samplerOpts...,
 295  			)
 296  		}))
 297  	}
 298  
 299  	if len(cfg.InitialFields) > 0 {
 300  		fs := make([]Field, 0, len(cfg.InitialFields))
 301  		keys := make([]string, 0, len(cfg.InitialFields))
 302  		for k := range cfg.InitialFields {
 303  			keys = append(keys, k)
 304  		}
 305  		sort.Strings(keys)
 306  		for _, k := range keys {
 307  			fs = append(fs, Any(k, cfg.InitialFields[k]))
 308  		}
 309  		opts = append(opts, Fields(fs...))
 310  	}
 311  
 312  	return opts
 313  }
 314  
 315  func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
 316  	sink, closeOut, err := Open(cfg.OutputPaths...)
 317  	if err != nil {
 318  		return nil, nil, err
 319  	}
 320  	errSink, _, err := Open(cfg.ErrorOutputPaths...)
 321  	if err != nil {
 322  		closeOut()
 323  		return nil, nil, err
 324  	}
 325  	return sink, errSink, nil
 326  }
 327  
 328  func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
 329  	return newEncoder(cfg.Encoding, cfg.EncoderConfig)
 330  }
 331