blob: 55637fb0b4b1004612b5b21576ca0d8f92c7b331 [file] [log] [blame]
Elia Battistonc8d0d462022-02-22 16:30:51 +01001// 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 "fmt"
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.
39type 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.
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.
98func NewProductionEncoderConfig() zapcore.EncoderConfig {
99 return zapcore.EncoderConfig{
100 TimeKey: "ts",
101 LevelKey: "level",
102 NameKey: "logger",
103 CallerKey: "caller",
104 FunctionKey: zapcore.OmitKey,
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.
120func 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.
137func 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",
144 FunctionKey: zapcore.OmitKey,
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.
161func 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.
173func (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
184 if cfg.Level == (AtomicLevel{}) {
185 return nil, fmt.Errorf("missing Level")
186 }
187
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
198func (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
217 if scfg := cfg.Sampling; scfg != nil {
218 opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
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 )
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
249func (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
262func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
263 return newEncoder(cfg.Encoding, cfg.EncoderConfig)
264}