William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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 ( |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 24 | "fmt" |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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 | // |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 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. |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 39 | type SamplingConfig struct { |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 40 | Initial int `json:"initial" yaml:"initial"` |
| 41 | Thereafter int `json:"thereafter" yaml:"thereafter"` |
| 42 | Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"` |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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 | func NewProductionEncoderConfig() zapcore.EncoderConfig { |
| 99 | return zapcore.EncoderConfig{ |
| 100 | TimeKey: "ts", |
| 101 | LevelKey: "level", |
| 102 | NameKey: "logger", |
| 103 | CallerKey: "caller", |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 104 | FunctionKey: zapcore.OmitKey, |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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. |
| 120 | func 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. |
| 137 | func 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", |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 144 | FunctionKey: zapcore.OmitKey, |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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. |
| 161 | func 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. |
| 173 | func (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 | |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 184 | if cfg.Level == (AtomicLevel{}) { |
| 185 | return nil, fmt.Errorf("missing Level") |
| 186 | } |
| 187 | |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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 | |
| 198 | func (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 | |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 217 | if scfg := cfg.Sampling; scfg != nil { |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 218 | opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { |
khenaidoo | 106c61a | 2021-08-11 18:05:46 -0400 | [diff] [blame] | 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 | ) |
William Kurkian | ea86948 | 2019-04-09 15:16:11 -0400 | [diff] [blame] | 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 | |
| 249 | func (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 | |
| 262 | func (cfg Config) buildEncoder() (zapcore.Encoder, error) { |
| 263 | return newEncoder(cfg.Encoding, cfg.EncoderConfig) |
| 264 | } |