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