Scott Baker | eee8dd8 | 2019-09-24 12:52:34 -0700 | [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 ( |
| 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. |
| 35 | type 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. |
| 53 | type 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 URLs or file paths to write logging output to. |
| 78 | // See Open for details. |
| 79 | OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` |
| 80 | // ErrorOutputPaths is a list of URLs 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. |
| 93 | func 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. |
| 114 | func 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. |
| 131 | func 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. |
| 154 | func 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. |
| 166 | func (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 | |
| 187 | func (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([]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 | |
| 228 | func (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 | |
| 241 | func (cfg Config) buildEncoder() (zapcore.Encoder, error) { |
| 242 | return newEncoder(cfg.Encoding, cfg.EncoderConfig) |
| 243 | } |