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 21package zap 22 23import ( 24 "sort" 25 "time" 26 27 "go.uber.org/zap/zapcore" 28) 29 30// SamplingConfig sets a sampling strategy for the logger. Sampling 31// caps the global CPU and I/O load that logging puts on your process while 32// attempting to preserve a representative subset of your logs. 33// 34// Values configured here are per-second. See zapcore.NewSampler for details. 35type SamplingConfig struct { 36 Initial int `json:"initial" yaml:"initial"` 37 Thereafter int `json:"thereafter" yaml:"thereafter"` 38} 39 40// Config offers a declarative way to construct a logger. 41// 42// It doesn't do anything that can't be done with New, Options, and the various 43// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to 44// toggle common options. 45type Config struct { 46 // Level is the minimum enabled logging level. Note that this is a dynamic 47 // level, so calling Config.Level.SetLevel will atomically change the log 48 // level of all loggers descended from this config. The zero value is 49 // InfoLevel. 50 Level AtomicLevel `json:"level" yaml:"level"` 51 // Development puts the logger in development mode, which changes the 52 // behavior of DPanicLevel and takes stacktraces more liberally. 53 Development bool `json:"development" yaml:"development"` 54 // DisableCaller stops annotating logs with the calling function's file 55 // name and line number. By default, all logs are annotated. 56 DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` 57 // DisableStacktrace completely disables automatic stacktrace capturing. By 58 // default, stacktraces are captured for WarnLevel and above logs in 59 // development and ErrorLevel and above in production. 60 DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` 61 // Sampling sets a sampling policy. A nil SamplingConfig disables sampling. 62 Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` 63 // Encoding sets the logger's encoding. Valid values are "json" and 64 // "console". 65 Encoding string `json:"encoding" yaml:"encoding"` 66 // EncoderConfig sets options for the chosen encoder. See 67 // zapcore.EncoderConfig for details. 68 EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` 69 // OutputPaths is a list of paths to write logging output to. See Open for 70 // details. 71 OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` 72 // ErrorOutputPaths is a list of paths to write internal logger errors to. 73 // The default is standard error. 74 ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` 75 // InitialFields is a collection of fields to add to the root logger. 76 InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` 77} 78 79// NewProductionEncoderConfig returns an opinionated EncoderConfig for 80// production environments. 81func NewProductionEncoderConfig() zapcore.EncoderConfig { 82 return zapcore.EncoderConfig{ 83 TimeKey: "ts", 84 LevelKey: "level", 85 NameKey: "logger", 86 CallerKey: "caller", 87 MessageKey: "msg", 88 StacktraceKey: "stacktrace", 89 LineEnding: zapcore.DefaultLineEnding, 90 EncodeLevel: zapcore.LowercaseLevelEncoder, 91 EncodeTime: zapcore.EpochTimeEncoder, 92 EncodeDuration: zapcore.SecondsDurationEncoder, 93 EncodeCaller: zapcore.ShortCallerEncoder, 94 } 95} 96 97// NewProductionConfig is the recommended production configuration. Logging is 98// enabled at InfoLevel and above. 99// 100// It uses a JSON encoder, writes to standard error, and enables sampling. 101// Stacktraces are automatically included on logs of ErrorLevel and above. 102func NewProductionConfig() Config { 103 return Config{ 104 Level: NewAtomicLevelAt(InfoLevel), 105 Development: false, 106 Sampling: &SamplingConfig{ 107 Initial: 100, 108 Thereafter: 100, 109 }, 110 Encoding: "json", 111 EncoderConfig: NewProductionEncoderConfig(), 112 OutputPaths: []string{"stderr"}, 113 ErrorOutputPaths: []string{"stderr"}, 114 } 115} 116 117// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for 118// development environments. 119func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { 120 return zapcore.EncoderConfig{ 121 // Keys can be anything except the empty string. 122 TimeKey: "T", 123 LevelKey: "L", 124 NameKey: "N", 125 CallerKey: "C", 126 MessageKey: "M", 127 StacktraceKey: "S", 128 LineEnding: zapcore.DefaultLineEnding, 129 EncodeLevel: zapcore.CapitalLevelEncoder, 130 EncodeTime: zapcore.ISO8601TimeEncoder, 131 EncodeDuration: zapcore.StringDurationEncoder, 132 EncodeCaller: zapcore.ShortCallerEncoder, 133 } 134} 135 136// NewDevelopmentConfig is a reasonable development configuration. Logging is 137// enabled at DebugLevel and above. 138// 139// It enables development mode (which makes DPanicLevel logs panic), uses a 140// console encoder, writes to standard error, and disables sampling. 141// Stacktraces are automatically included on logs of WarnLevel and above. 142func NewDevelopmentConfig() Config { 143 return Config{ 144 Level: NewAtomicLevelAt(DebugLevel), 145 Development: true, 146 Encoding: "console", 147 EncoderConfig: NewDevelopmentEncoderConfig(), 148 OutputPaths: []string{"stderr"}, 149 ErrorOutputPaths: []string{"stderr"}, 150 } 151} 152 153// Build constructs a logger from the Config and Options. 154func (cfg Config) Build(opts ...Option) (*Logger, error) { 155 enc, err := cfg.buildEncoder() 156 if err != nil { 157 return nil, err 158 } 159 160 sink, errSink, err := cfg.openSinks() 161 if err != nil { 162 return nil, err 163 } 164 165 log := New( 166 zapcore.NewCore(enc, sink, cfg.Level), 167 cfg.buildOptions(errSink)..., 168 ) 169 if len(opts) > 0 { 170 log = log.WithOptions(opts...) 171 } 172 return log, nil 173} 174 175func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { 176 opts := []Option{ErrorOutput(errSink)} 177 178 if cfg.Development { 179 opts = append(opts, Development()) 180 } 181 182 if !cfg.DisableCaller { 183 opts = append(opts, AddCaller()) 184 } 185 186 stackLevel := ErrorLevel 187 if cfg.Development { 188 stackLevel = WarnLevel 189 } 190 if !cfg.DisableStacktrace { 191 opts = append(opts, AddStacktrace(stackLevel)) 192 } 193 194 if cfg.Sampling != nil { 195 opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { 196 return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter)) 197 })) 198 } 199 200 if len(cfg.InitialFields) > 0 { 201 fs := make([]zapcore.Field, 0, len(cfg.InitialFields)) 202 keys := make([]string, 0, len(cfg.InitialFields)) 203 for k := range cfg.InitialFields { 204 keys = append(keys, k) 205 } 206 sort.Strings(keys) 207 for _, k := range keys { 208 fs = append(fs, Any(k, cfg.InitialFields[k])) 209 } 210 opts = append(opts, Fields(fs...)) 211 } 212 213 return opts 214} 215 216func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { 217 sink, closeOut, err := Open(cfg.OutputPaths...) 218 if err != nil { 219 return nil, nil, err 220 } 221 errSink, _, err := Open(cfg.ErrorOutputPaths...) 222 if err != nil { 223 closeOut() 224 return nil, nil, err 225 } 226 return sink, errSink, nil 227} 228 229func (cfg Config) buildEncoder() (zapcore.Encoder, error) { 230 return newEncoder(cfg.Encoding, cfg.EncoderConfig) 231} 232