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 "fmt" 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. 39type 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. 58type 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. 98func NewProductionEncoderConfig() zapcore.EncoderConfig { 99 return zapcore.EncoderConfig{ 100 TimeKey: "ts", 101 LevelKey: "level", 102 NameKey: "logger", 103 CallerKey: "caller", 104 FunctionKey: zapcore.OmitKey, 105 MessageKey: "msg", 106 StacktraceKey: "stacktrace", 107 LineEnding: zapcore.DefaultLineEnding, 108 EncodeLevel: zapcore.LowercaseLevelEncoder, 109 EncodeTime: zapcore.EpochTimeEncoder, 110 EncodeDuration: zapcore.SecondsDurationEncoder, 111 EncodeCaller: zapcore.ShortCallerEncoder, 112 } 113} 114 115// NewProductionConfig is a reasonable production logging configuration. 116// Logging is enabled at InfoLevel and above. 117// 118// It uses a JSON encoder, writes to standard error, and enables sampling. 119// Stacktraces are automatically included on logs of ErrorLevel and above. 120func NewProductionConfig() Config { 121 return Config{ 122 Level: NewAtomicLevelAt(InfoLevel), 123 Development: false, 124 Sampling: &SamplingConfig{ 125 Initial: 100, 126 Thereafter: 100, 127 }, 128 Encoding: "json", 129 EncoderConfig: NewProductionEncoderConfig(), 130 OutputPaths: []string{"stderr"}, 131 ErrorOutputPaths: []string{"stderr"}, 132 } 133} 134 135// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for 136// development environments. 137func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { 138 return zapcore.EncoderConfig{ 139 // Keys can be anything except the empty string. 140 TimeKey: "T", 141 LevelKey: "L", 142 NameKey: "N", 143 CallerKey: "C", 144 FunctionKey: zapcore.OmitKey, 145 MessageKey: "M", 146 StacktraceKey: "S", 147 LineEnding: zapcore.DefaultLineEnding, 148 EncodeLevel: zapcore.CapitalLevelEncoder, 149 EncodeTime: zapcore.ISO8601TimeEncoder, 150 EncodeDuration: zapcore.StringDurationEncoder, 151 EncodeCaller: zapcore.ShortCallerEncoder, 152 } 153} 154 155// NewDevelopmentConfig is a reasonable development logging configuration. 156// Logging is enabled at DebugLevel and above. 157// 158// It enables development mode (which makes DPanicLevel logs panic), uses a 159// console encoder, writes to standard error, and disables sampling. 160// Stacktraces are automatically included on logs of WarnLevel and above. 161func NewDevelopmentConfig() Config { 162 return Config{ 163 Level: NewAtomicLevelAt(DebugLevel), 164 Development: true, 165 Encoding: "console", 166 EncoderConfig: NewDevelopmentEncoderConfig(), 167 OutputPaths: []string{"stderr"}, 168 ErrorOutputPaths: []string{"stderr"}, 169 } 170} 171 172// Build constructs a logger from the Config and Options. 173func (cfg Config) Build(opts ...Option) (*Logger, error) { 174 enc, err := cfg.buildEncoder() 175 if err != nil { 176 return nil, err 177 } 178 179 sink, errSink, err := cfg.openSinks() 180 if err != nil { 181 return nil, err 182 } 183 184 if cfg.Level == (AtomicLevel{}) { 185 return nil, fmt.Errorf("missing Level") 186 } 187 188 log := New( 189 zapcore.NewCore(enc, sink, cfg.Level), 190 cfg.buildOptions(errSink)..., 191 ) 192 if len(opts) > 0 { 193 log = log.WithOptions(opts...) 194 } 195 return log, nil 196} 197 198func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { 199 opts := []Option{ErrorOutput(errSink)} 200 201 if cfg.Development { 202 opts = append(opts, Development()) 203 } 204 205 if !cfg.DisableCaller { 206 opts = append(opts, AddCaller()) 207 } 208 209 stackLevel := ErrorLevel 210 if cfg.Development { 211 stackLevel = WarnLevel 212 } 213 if !cfg.DisableStacktrace { 214 opts = append(opts, AddStacktrace(stackLevel)) 215 } 216 217 if scfg := cfg.Sampling; scfg != nil { 218 opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { 219 var samplerOpts []zapcore.SamplerOption 220 if scfg.Hook != nil { 221 samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook)) 222 } 223 return zapcore.NewSamplerWithOptions( 224 core, 225 time.Second, 226 cfg.Sampling.Initial, 227 cfg.Sampling.Thereafter, 228 samplerOpts..., 229 ) 230 })) 231 } 232 233 if len(cfg.InitialFields) > 0 { 234 fs := make([]Field, 0, len(cfg.InitialFields)) 235 keys := make([]string, 0, len(cfg.InitialFields)) 236 for k := range cfg.InitialFields { 237 keys = append(keys, k) 238 } 239 sort.Strings(keys) 240 for _, k := range keys { 241 fs = append(fs, Any(k, cfg.InitialFields[k])) 242 } 243 opts = append(opts, Fields(fs...)) 244 } 245 246 return opts 247} 248 249func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { 250 sink, closeOut, err := Open(cfg.OutputPaths...) 251 if err != nil { 252 return nil, nil, err 253 } 254 errSink, _, err := Open(cfg.ErrorOutputPaths...) 255 if err != nil { 256 closeOut() 257 return nil, nil, err 258 } 259 return sink, errSink, nil 260} 261 262func (cfg Config) buildEncoder() (zapcore.Encoder, error) { 263 return newEncoder(cfg.Encoding, cfg.EncoderConfig) 264} 265