// Copyright (c) 2016 Uber Technologies, Inc.1//2// Permission is hereby granted, free of charge, to any person obtaining a copy3// of this software and associated documentation files (the "Software"), to deal4// in the Software without restriction, including without limitation the rights5// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell6// copies of the Software, and to permit persons to whom the Software is7// furnished to do so, subject to the following conditions:8//9// The above copyright notice and this permission notice shall be included in10// all copies or substantial portions of the Software.11//12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER16// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN18// THE SOFTWARE.1920package zap2122import (23"errors"24"sort"25"time"2627"go.uber.org/zap/zapcore"28)2930// SamplingConfig sets a sampling strategy for the logger. Sampling caps the31// global CPU and I/O load that logging puts on your process while attempting32// to preserve a representative subset of your logs.33//34// If specified, the Sampler will invoke the Hook after each decision.35//36// Values configured here are per-second. See zapcore.NewSamplerWithOptions for37// details.38type SamplingConfig struct {39Initial int `json:"initial" yaml:"initial"`40Thereafter int `json:"thereafter" yaml:"thereafter"`41Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"`42}4344// Config offers a declarative way to construct a logger. It doesn't do45// anything that can't be done with New, Options, and the various46// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to47// toggle common options.48//49// Note that Config intentionally supports only the most common options. More50// unusual logging setups (logging to network connections or message queues,51// splitting output between multiple files, etc.) are possible, but require52// direct use of the zapcore package. For sample code, see the package-level53// BasicConfiguration and AdvancedConfiguration examples.54//55// For an example showing runtime log level changes, see the documentation for56// AtomicLevel.57type Config struct {58// Level is the minimum enabled logging level. Note that this is a dynamic59// level, so calling Config.Level.SetLevel will atomically change the log60// level of all loggers descended from this config.61Level AtomicLevel `json:"level" yaml:"level"`62// Development puts the logger in development mode, which changes the63// behavior of DPanicLevel and takes stacktraces more liberally.64Development bool `json:"development" yaml:"development"`65// DisableCaller stops annotating logs with the calling function's file66// name and line number. By default, all logs are annotated.67DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`68// DisableStacktrace completely disables automatic stacktrace capturing. By69// default, stacktraces are captured for WarnLevel and above logs in70// development and ErrorLevel and above in production.71DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`72// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.73Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`74// Encoding sets the logger's encoding. Valid values are "json" and75// "console", as well as any third-party encodings registered via76// RegisterEncoder.77Encoding string `json:"encoding" yaml:"encoding"`78// EncoderConfig sets options for the chosen encoder. See79// zapcore.EncoderConfig for details.80EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`81// OutputPaths is a list of URLs or file paths to write logging output to.82// See Open for details.83OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`84// ErrorOutputPaths is a list of URLs to write internal logger errors to.85// The default is standard error.86//87// Note that this setting only affects internal errors; for sample code that88// sends error-level logs to a different location from info- and debug-level89// logs, see the package-level AdvancedConfiguration example.90ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`91// InitialFields is a collection of fields to add to the root logger.92InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`93}9495// NewProductionEncoderConfig returns an opinionated EncoderConfig for96// production environments.97//98// Messages encoded with this configuration will be JSON-formatted99// and will have the following keys by default:100//101// - "level": The logging level (e.g. "info", "error").102// - "ts": The current time in number of seconds since the Unix epoch.103// - "msg": The message passed to the log statement.104// - "caller": If available, a short path to the file and line number105// where the log statement was issued.106// The logger configuration determines whether this field is captured.107// - "stacktrace": If available, a stack trace from the line108// where the log statement was issued.109// The logger configuration determines whether this field is captured.110//111// By default, the following formats are used for different types:112//113// - Time is formatted as floating-point number of seconds since the Unix114// epoch.115// - Duration is formatted as floating-point number of seconds.116//117// You may change these by setting the appropriate fields in the returned118// object.119// For example, use the following to change the time encoding format:120//121// cfg := zap.NewProductionEncoderConfig()122// cfg.EncodeTime = zapcore.ISO8601TimeEncoder123func NewProductionEncoderConfig() zapcore.EncoderConfig {124return zapcore.EncoderConfig{125TimeKey: "ts",126LevelKey: "level",127NameKey: "logger",128CallerKey: "caller",129FunctionKey: zapcore.OmitKey,130MessageKey: "msg",131StacktraceKey: "stacktrace",132LineEnding: zapcore.DefaultLineEnding,133EncodeLevel: zapcore.LowercaseLevelEncoder,134EncodeTime: zapcore.EpochTimeEncoder,135EncodeDuration: zapcore.SecondsDurationEncoder,136EncodeCaller: zapcore.ShortCallerEncoder,137}138}139140// NewProductionConfig builds a reasonable default production logging141// configuration.142// Logging is enabled at InfoLevel and above, and uses a JSON encoder.143// Logs are written to standard error.144// Stacktraces are included on logs of ErrorLevel and above.145// DPanicLevel logs will not panic, but will write a stacktrace.146//147// Sampling is enabled at 100:100 by default,148// meaning that after the first 100 log entries149// with the same level and message in the same second,150// it will log every 100th entry151// with the same level and message in the same second.152// You may disable this behavior by setting Sampling to nil.153//154// See [NewProductionEncoderConfig] for information155// on the default encoder configuration.156func NewProductionConfig() Config {157return Config{158Level: NewAtomicLevelAt(InfoLevel),159Development: false,160Sampling: &SamplingConfig{161Initial: 100,162Thereafter: 100,163},164Encoding: "json",165EncoderConfig: NewProductionEncoderConfig(),166OutputPaths: []string{"stderr"},167ErrorOutputPaths: []string{"stderr"},168}169}170171// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for172// development environments.173//174// Messages encoded with this configuration will use Zap's console encoder175// intended to print human-readable output.176// It will print log messages with the following information:177//178// - The log level (e.g. "INFO", "ERROR").179// - The time in ISO8601 format (e.g. "2017-01-01T12:00:00Z").180// - The message passed to the log statement.181// - If available, a short path to the file and line number182// where the log statement was issued.183// The logger configuration determines whether this field is captured.184// - If available, a stacktrace from the line185// where the log statement was issued.186// The logger configuration determines whether this field is captured.187//188// By default, the following formats are used for different types:189//190// - Time is formatted in ISO8601 format (e.g. "2017-01-01T12:00:00Z").191// - Duration is formatted as a string (e.g. "1.234s").192//193// You may change these by setting the appropriate fields in the returned194// object.195// For example, use the following to change the time encoding format:196//197// cfg := zap.NewDevelopmentEncoderConfig()198// cfg.EncodeTime = zapcore.ISO8601TimeEncoder199func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {200return zapcore.EncoderConfig{201// Keys can be anything except the empty string.202TimeKey: "T",203LevelKey: "L",204NameKey: "N",205CallerKey: "C",206FunctionKey: zapcore.OmitKey,207MessageKey: "M",208StacktraceKey: "S",209LineEnding: zapcore.DefaultLineEnding,210EncodeLevel: zapcore.CapitalLevelEncoder,211EncodeTime: zapcore.ISO8601TimeEncoder,212EncodeDuration: zapcore.StringDurationEncoder,213EncodeCaller: zapcore.ShortCallerEncoder,214}215}216217// NewDevelopmentConfig builds a reasonable default development logging218// configuration.219// Logging is enabled at DebugLevel and above, and uses a console encoder.220// Logs are written to standard error.221// Stacktraces are included on logs of WarnLevel and above.222// DPanicLevel logs will panic.223//224// See [NewDevelopmentEncoderConfig] for information225// on the default encoder configuration.226func NewDevelopmentConfig() Config {227return Config{228Level: NewAtomicLevelAt(DebugLevel),229Development: true,230Encoding: "console",231EncoderConfig: NewDevelopmentEncoderConfig(),232OutputPaths: []string{"stderr"},233ErrorOutputPaths: []string{"stderr"},234}235}236237// Build constructs a logger from the Config and Options.238func (cfg Config) Build(opts ...Option) (*Logger, error) {239enc, err := cfg.buildEncoder()240if err != nil {241return nil, err242}243244sink, errSink, err := cfg.openSinks()245if err != nil {246return nil, err247}248249if cfg.Level == (AtomicLevel{}) {250return nil, errors.New("missing Level")251}252253log := New(254zapcore.NewCore(enc, sink, cfg.Level),255cfg.buildOptions(errSink)...,256)257if len(opts) > 0 {258log = log.WithOptions(opts...)259}260return log, nil261}262263func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {264opts := []Option{ErrorOutput(errSink)}265266if cfg.Development {267opts = append(opts, Development())268}269270if !cfg.DisableCaller {271opts = append(opts, AddCaller())272}273274stackLevel := ErrorLevel275if cfg.Development {276stackLevel = WarnLevel277}278if !cfg.DisableStacktrace {279opts = append(opts, AddStacktrace(stackLevel))280}281282if scfg := cfg.Sampling; scfg != nil {283opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {284var samplerOpts []zapcore.SamplerOption285if scfg.Hook != nil {286samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook))287}288return zapcore.NewSamplerWithOptions(289core,290time.Second,291cfg.Sampling.Initial,292cfg.Sampling.Thereafter,293samplerOpts...,294)295}))296}297298if len(cfg.InitialFields) > 0 {299fs := make([]Field, 0, len(cfg.InitialFields))300keys := make([]string, 0, len(cfg.InitialFields))301for k := range cfg.InitialFields {302keys = append(keys, k)303}304sort.Strings(keys)305for _, k := range keys {306fs = append(fs, Any(k, cfg.InitialFields[k]))307}308opts = append(opts, Fields(fs...))309}310311return opts312}313314func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {315sink, closeOut, err := Open(cfg.OutputPaths...)316if err != nil {317return nil, nil, err318}319errSink, _, err := Open(cfg.ErrorOutputPaths...)320if err != nil {321closeOut()322return nil, nil, err323}324return sink, errSink, nil325}326327func (cfg Config) buildEncoder() (zapcore.Encoder, error) {328return newEncoder(cfg.Encoding, cfg.EncoderConfig)329}330331332