// Copyright (c) 2016 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package zap import ( "sort" "time" "github.com/finogeeks/ligase/skunkworks/zap/zapcore" ) // SamplingConfig sets a sampling strategy for the logger. Sampling caps the // global CPU and I/O load that logging puts on your process while attempting // to preserve a representative subset of your logs. // // Values configured here are per-second. See zapcore.NewSampler for details. type SamplingConfig struct { Initial int `json:"initial" yaml:"initial"` Thereafter int `json:"thereafter" yaml:"thereafter"` } // Config offers a declarative way to construct a logger. It doesn't do // anything that can't be done with New, Options, and the various // zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to // toggle common options. // // Note that Config intentionally supports only the most common options. More // unusual logging setups (logging to network connections or message queues, // splitting output between multiple files, etc.) are possible, but require // direct use of the zapcore package. For sample code, see the package-level // BasicConfiguration and AdvancedConfiguration examples. // // For an example showing runtime log level changes, see the documentation for // AtomicLevel. type Config struct { // Level is the minimum enabled logging level. Note that this is a dynamic // level, so calling Config.Level.SetLevel will atomically change the log // level of all loggers descended from this config. Level AtomicLevel `json:"level" yaml:"level"` // Development puts the logger in development mode, which changes the // behavior of DPanicLevel and takes stacktraces more liberally. Development bool `json:"development" yaml:"development"` // DisableCaller stops annotating logs with the calling function's file // name and line number. By default, all logs are annotated. DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` // DisableStacktrace completely disables automatic stacktrace capturing. By // default, stacktraces are captured for WarnLevel and above logs in // development and ErrorLevel and above in production. DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` // Sampling sets a sampling policy. A nil SamplingConfig disables sampling. Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` // Encoding sets the logger's encoding. Valid values are "json" and // "console", as well as any third-party encodings registered via // RegisterEncoder. Encoding string `json:"encoding" yaml:"encoding"` // EncoderConfig sets options for the chosen encoder. See // zapcore.EncoderConfig for details. EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` // OutputPaths is a list of paths to write logging output to. See Open for // details. OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` // ErrorOutputPaths is a list of paths to write internal logger errors to. // The default is standard error. // // Note that this setting only affects internal errors; for sample code that // sends error-level logs to a different location from info- and debug-level // logs, see the package-level AdvancedConfiguration example. ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` // InitialFields is a collection of fields to add to the root logger. InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` } // NewProductionEncoderConfig returns an opinionated EncoderConfig for // production environments. func NewProductionEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.EpochTimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } } // NewProductionConfig is a reasonable production logging configuration. // Logging is enabled at InfoLevel and above. // // It uses a JSON encoder, writes to standard error, and enables sampling. // Stacktraces are automatically included on logs of ErrorLevel and above. func NewProductionConfig() Config { return Config{ Level: NewAtomicLevelAt(InfoLevel), Development: false, Sampling: &SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: "json", EncoderConfig: NewProductionEncoderConfig(), OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } } // NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for // development environments. func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ // Keys can be anything except the empty string. TimeKey: "T", LevelKey: "L", NameKey: "N", CallerKey: "C", MessageKey: "M", StacktraceKey: "S", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.CapitalLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } } // NewDevelopmentConfig is a reasonable development logging configuration. // Logging is enabled at DebugLevel and above. // // It enables development mode (which makes DPanicLevel logs panic), uses a // console encoder, writes to standard error, and disables sampling. // Stacktraces are automatically included on logs of WarnLevel and above. func NewDevelopmentConfig() Config { return Config{ Level: NewAtomicLevelAt(DebugLevel), Development: true, Encoding: "console", EncoderConfig: NewDevelopmentEncoderConfig(), OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } } // Build constructs a logger from the Config and Options. func (cfg Config) Build(opts ...Option) (*Logger, error) { enc, err := cfg.buildEncoder() if err != nil { return nil, err } sink, errSink, err := cfg.openSinks() if err != nil { return nil, err } log := New( zapcore.NewCore(enc, sink, cfg.Level), cfg.buildOptions(errSink)..., ) if len(opts) > 0 { log = log.WithOptions(opts...) } return log, nil } func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { opts := []Option{ErrorOutput(errSink)} if cfg.Development { opts = append(opts, Development()) } if !cfg.DisableCaller { opts = append(opts, AddCaller()) } stackLevel := ErrorLevel if cfg.Development { stackLevel = WarnLevel } if !cfg.DisableStacktrace { opts = append(opts, AddStacktrace(stackLevel)) } if cfg.Sampling != nil { opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter)) })) } if len(cfg.InitialFields) > 0 { fs := make([]zapcore.Field, 0, len(cfg.InitialFields)) keys := make([]string, 0, len(cfg.InitialFields)) for k := range cfg.InitialFields { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fs = append(fs, Any(k, cfg.InitialFields[k])) } opts = append(opts, Fields(fs...)) } return opts } func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { sink, closeOut, err := Open(cfg.OutputPaths...) if err != nil { return nil, nil, err } errSink, _, err := Open(cfg.ErrorOutputPaths...) if err != nil { closeOut() return nil, nil, err } return sink, errSink, nil } func (cfg Config) buildEncoder() (zapcore.Encoder, error) { return newEncoder(cfg.Encoding, cfg.EncoderConfig) }