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
21package zap
22
23import (
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.
35type 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.
53type 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 paths to write logging output to. See Open for
78	// details.
79	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
80	// ErrorOutputPaths is a list of paths 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.
93func 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.
114func 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.
131func 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.
154func 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.
166func (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
187func (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([]zapcore.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
228func (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
241func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
242	return newEncoder(cfg.Encoding, cfg.EncoderConfig)
243}
244