...

Source file src/go.uber.org/zap/config.go

Documentation: go.uber.org/zap

     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  

View as plain text