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