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
31// caps the global CPU and I/O load that logging puts on your process while
32// attempting 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.
41//
42// It doesn't do anything that can't be done with New, Options, and the various
43// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
44// toggle common options.
45type Config struct {
46	// Level is the minimum enabled logging level. Note that this is a dynamic
47	// level, so calling Config.Level.SetLevel will atomically change the log
48	// level of all loggers descended from this config. The zero value is
49	// InfoLevel.
50	Level AtomicLevel `json:"level" yaml:"level"`
51	// Development puts the logger in development mode, which changes the
52	// behavior of DPanicLevel and takes stacktraces more liberally.
53	Development bool `json:"development" yaml:"development"`
54	// DisableCaller stops annotating logs with the calling function's file
55	// name and line number. By default, all logs are annotated.
56	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
57	// DisableStacktrace completely disables automatic stacktrace capturing. By
58	// default, stacktraces are captured for WarnLevel and above logs in
59	// development and ErrorLevel and above in production.
60	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
61	// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
62	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
63	// Encoding sets the logger's encoding. Valid values are "json" and
64	// "console".
65	Encoding string `json:"encoding" yaml:"encoding"`
66	// EncoderConfig sets options for the chosen encoder. See
67	// zapcore.EncoderConfig for details.
68	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
69	// OutputPaths is a list of paths to write logging output to. See Open for
70	// details.
71	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
72	// ErrorOutputPaths is a list of paths to write internal logger errors to.
73	// The default is standard error.
74	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
75	// InitialFields is a collection of fields to add to the root logger.
76	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
77}
78
79// NewProductionEncoderConfig returns an opinionated EncoderConfig for
80// production environments.
81func NewProductionEncoderConfig() zapcore.EncoderConfig {
82	return zapcore.EncoderConfig{
83		TimeKey:        "ts",
84		LevelKey:       "level",
85		NameKey:        "logger",
86		CallerKey:      "caller",
87		MessageKey:     "msg",
88		StacktraceKey:  "stacktrace",
89		LineEnding:     zapcore.DefaultLineEnding,
90		EncodeLevel:    zapcore.LowercaseLevelEncoder,
91		EncodeTime:     zapcore.EpochTimeEncoder,
92		EncodeDuration: zapcore.SecondsDurationEncoder,
93		EncodeCaller:   zapcore.ShortCallerEncoder,
94	}
95}
96
97// NewProductionConfig is the recommended production configuration. Logging is
98// enabled at InfoLevel and above.
99//
100// It uses a JSON encoder, writes to standard error, and enables sampling.
101// Stacktraces are automatically included on logs of ErrorLevel and above.
102func NewProductionConfig() Config {
103	return Config{
104		Level:       NewAtomicLevelAt(InfoLevel),
105		Development: false,
106		Sampling: &SamplingConfig{
107			Initial:    100,
108			Thereafter: 100,
109		},
110		Encoding:         "json",
111		EncoderConfig:    NewProductionEncoderConfig(),
112		OutputPaths:      []string{"stderr"},
113		ErrorOutputPaths: []string{"stderr"},
114	}
115}
116
117// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
118// development environments.
119func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
120	return zapcore.EncoderConfig{
121		// Keys can be anything except the empty string.
122		TimeKey:        "T",
123		LevelKey:       "L",
124		NameKey:        "N",
125		CallerKey:      "C",
126		MessageKey:     "M",
127		StacktraceKey:  "S",
128		LineEnding:     zapcore.DefaultLineEnding,
129		EncodeLevel:    zapcore.CapitalLevelEncoder,
130		EncodeTime:     zapcore.ISO8601TimeEncoder,
131		EncodeDuration: zapcore.StringDurationEncoder,
132		EncodeCaller:   zapcore.ShortCallerEncoder,
133	}
134}
135
136// NewDevelopmentConfig is a reasonable development configuration. Logging is
137// enabled at DebugLevel and above.
138//
139// It enables development mode (which makes DPanicLevel logs panic), uses a
140// console encoder, writes to standard error, and disables sampling.
141// Stacktraces are automatically included on logs of WarnLevel and above.
142func NewDevelopmentConfig() Config {
143	return Config{
144		Level:            NewAtomicLevelAt(DebugLevel),
145		Development:      true,
146		Encoding:         "console",
147		EncoderConfig:    NewDevelopmentEncoderConfig(),
148		OutputPaths:      []string{"stderr"},
149		ErrorOutputPaths: []string{"stderr"},
150	}
151}
152
153// Build constructs a logger from the Config and Options.
154func (cfg Config) Build(opts ...Option) (*Logger, error) {
155	enc, err := cfg.buildEncoder()
156	if err != nil {
157		return nil, err
158	}
159
160	sink, errSink, err := cfg.openSinks()
161	if err != nil {
162		return nil, err
163	}
164
165	log := New(
166		zapcore.NewCore(enc, sink, cfg.Level),
167		cfg.buildOptions(errSink)...,
168	)
169	if len(opts) > 0 {
170		log = log.WithOptions(opts...)
171	}
172	return log, nil
173}
174
175func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
176	opts := []Option{ErrorOutput(errSink)}
177
178	if cfg.Development {
179		opts = append(opts, Development())
180	}
181
182	if !cfg.DisableCaller {
183		opts = append(opts, AddCaller())
184	}
185
186	stackLevel := ErrorLevel
187	if cfg.Development {
188		stackLevel = WarnLevel
189	}
190	if !cfg.DisableStacktrace {
191		opts = append(opts, AddStacktrace(stackLevel))
192	}
193
194	if cfg.Sampling != nil {
195		opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
196			return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter))
197		}))
198	}
199
200	if len(cfg.InitialFields) > 0 {
201		fs := make([]zapcore.Field, 0, len(cfg.InitialFields))
202		keys := make([]string, 0, len(cfg.InitialFields))
203		for k := range cfg.InitialFields {
204			keys = append(keys, k)
205		}
206		sort.Strings(keys)
207		for _, k := range keys {
208			fs = append(fs, Any(k, cfg.InitialFields[k]))
209		}
210		opts = append(opts, Fields(fs...))
211	}
212
213	return opts
214}
215
216func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
217	sink, closeOut, err := Open(cfg.OutputPaths...)
218	if err != nil {
219		return nil, nil, err
220	}
221	errSink, _, err := Open(cfg.ErrorOutputPaths...)
222	if err != nil {
223		closeOut()
224		return nil, nil, err
225	}
226	return sink, errSink, nil
227}
228
229func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
230	return newEncoder(cfg.Encoding, cfg.EncoderConfig)
231}
232