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