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