blob: e76e4e64fbecc46808f83a1ef2e8d788702b668d [file] [log] [blame]
William Kurkianea869482019-04-09 15:16:11 -04001// 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 (
Abhay Kumara61c5222025-11-10 07:32:50 +000024 "errors"
William Kurkianea869482019-04-09 15:16:11 -040025 "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//
khenaidoo106c61a2021-08-11 18:05:46 -040035// 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 Kurkianea869482019-04-09 15:16:11 -040039type SamplingConfig struct {
khenaidoo106c61a2021-08-11 18:05:46 -040040 Initial int `json:"initial" yaml:"initial"`
41 Thereafter int `json:"thereafter" yaml:"thereafter"`
42 Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"`
William Kurkianea869482019-04-09 15:16:11 -040043}
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.
58type 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.
Abhay Kumara61c5222025-11-10 07:32:50 +000098//
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
William Kurkianea869482019-04-09 15:16:11 -0400124func NewProductionEncoderConfig() zapcore.EncoderConfig {
125 return zapcore.EncoderConfig{
126 TimeKey: "ts",
127 LevelKey: "level",
128 NameKey: "logger",
129 CallerKey: "caller",
khenaidoo106c61a2021-08-11 18:05:46 -0400130 FunctionKey: zapcore.OmitKey,
William Kurkianea869482019-04-09 15:16:11 -0400131 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
Abhay Kumara61c5222025-11-10 07:32:50 +0000141// 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.
William Kurkianea869482019-04-09 15:16:11 -0400147//
Abhay Kumara61c5222025-11-10 07:32:50 +0000148// 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.
William Kurkianea869482019-04-09 15:16:11 -0400157func 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.
Abhay Kumara61c5222025-11-10 07:32:50 +0000174//
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
William Kurkianea869482019-04-09 15:16:11 -0400200func 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",
khenaidoo106c61a2021-08-11 18:05:46 -0400207 FunctionKey: zapcore.OmitKey,
William Kurkianea869482019-04-09 15:16:11 -0400208 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
Abhay Kumara61c5222025-11-10 07:32:50 +0000218// 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.
William Kurkianea869482019-04-09 15:16:11 -0400224//
Abhay Kumara61c5222025-11-10 07:32:50 +0000225// See [NewDevelopmentEncoderConfig] for information
226// on the default encoder configuration.
William Kurkianea869482019-04-09 15:16:11 -0400227func 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.
239func (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
khenaidoo106c61a2021-08-11 18:05:46 -0400250 if cfg.Level == (AtomicLevel{}) {
Abhay Kumara61c5222025-11-10 07:32:50 +0000251 return nil, errors.New("missing Level")
khenaidoo106c61a2021-08-11 18:05:46 -0400252 }
253
William Kurkianea869482019-04-09 15:16:11 -0400254 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
264func (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
khenaidoo106c61a2021-08-11 18:05:46 -0400283 if scfg := cfg.Sampling; scfg != nil {
William Kurkianea869482019-04-09 15:16:11 -0400284 opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
khenaidoo106c61a2021-08-11 18:05:46 -0400285 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 )
William Kurkianea869482019-04-09 15:16:11 -0400296 }))
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
315func (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
328func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
329 return newEncoder(cfg.Encoding, cfg.EncoderConfig)
330}