-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfactory.go
399 lines (355 loc) · 9.67 KB
/
factory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
package flume
import (
"fmt"
"github.com/ansel1/merry"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"strings"
"sync"
)
type loggerInfo struct {
levelEnabler zapcore.LevelEnabler
atomicInnerCore atomicInnerCore
}
// Factory is a log management core. It spawns loggers. The Factory has
// methods for dynamically reconfiguring all the loggers spawned from Factory.
//
// The flume package has mirrors of most of the functions which delegate to a
// default, package-level factory.
type Factory struct {
defaultLevel zap.AtomicLevel
encoder zapcore.Encoder
out io.Writer
loggers map[string]*loggerInfo
sync.Mutex
addCaller bool
hooks []HookFunc
}
// Encoder serializes log entries. Re-exported from zap for now to avoid exporting zap.
type Encoder zapcore.Encoder
// NewFactory returns a factory. The default level is set to OFF (all logs disabled)
func NewFactory() *Factory {
f := Factory{
defaultLevel: zap.NewAtomicLevel(),
loggers: map[string]*loggerInfo{},
}
f.SetDefaultLevel(OffLevel)
return &f
}
func (r *Factory) getEncoder() zapcore.Encoder {
if r.encoder == nil {
return NewLTSVEncoder(NewEncoderConfig())
}
return r.encoder
}
// SetEncoder sets the encoder for all loggers created by (in the past or future) this factory.
func (r *Factory) SetEncoder(e Encoder) {
r.Lock()
defer r.Unlock()
r.encoder = e
r.refreshLoggers()
}
// SetOut sets the output writer for all logs produced by this factory.
// Returns a function which sets the output writer back to the prior setting.
func (r *Factory) SetOut(w io.Writer) func() {
r.Lock()
defer r.Unlock()
prior := r.out
r.out = w
r.refreshLoggers()
return func() {
r.SetOut(prior)
}
}
// SetAddCaller enables adding the logging callsite (file and line number) to the log entries.
func (r *Factory) SetAddCaller(b bool) {
r.Lock()
defer r.Unlock()
r.addCaller = b
r.refreshLoggers()
}
func (r *Factory) getOut() io.Writer {
if r.out == nil {
return os.Stdout
}
return r.out
}
func (r *Factory) refreshLoggers() {
for name, info := range r.loggers {
info.atomicInnerCore.set(r.newInnerCore(name, info))
}
}
func (r *Factory) getLoggerInfo(name string) *loggerInfo {
info, found := r.loggers[name]
if !found {
info = &loggerInfo{}
r.loggers[name] = info
info.atomicInnerCore.set(r.newInnerCore(name, info))
}
return info
}
func (r *Factory) newInnerCore(name string, info *loggerInfo) *innerCore {
var l zapcore.LevelEnabler
switch {
case info.levelEnabler != nil:
l = info.levelEnabler
default:
l = r.defaultLevel
}
zc := zapcore.NewCore(
r.getEncoder(),
zapcore.AddSync(r.getOut()),
l,
)
return &innerCore{
name: name,
Core: zc,
addCaller: r.addCaller,
errorOutput: zapcore.AddSync(os.Stderr),
hooks: r.hooks,
}
}
// NewLogger returns a new Logger
func (r *Factory) NewLogger(name string) Logger {
return r.NewCore(name)
}
// NewCore returns a new Core.
func (r *Factory) NewCore(name string, options ...CoreOption) *Core {
r.Lock()
defer r.Unlock()
info := r.getLoggerInfo(name)
core := &Core{
atomicInnerCore: &info.atomicInnerCore,
}
for _, opt := range options {
opt.apply(core)
}
return core
}
func (r *Factory) setLevel(name string, l Level) {
info := r.getLoggerInfo(name)
info.levelEnabler = zapcore.Level(l)
}
// SetLevel sets the log level for a particular named logger. All loggers with this same
// are affected, in the past or future.
func (r *Factory) SetLevel(name string, l Level) {
r.Lock()
defer r.Unlock()
r.setLevel(name, l)
r.refreshLoggers()
}
// SetDefaultLevel sets the default log level for all loggers which don't have a specific level
// assigned to them
func (r *Factory) SetDefaultLevel(l Level) {
r.defaultLevel.SetLevel(zapcore.Level(l))
}
type Entry = zapcore.Entry
type CheckedEntry = zapcore.CheckedEntry
type Field = zapcore.Field
// HookFunc adapts a single function to the Hook interface.
type HookFunc func(*CheckedEntry, []Field) []Field
// Hooks adds functions which are called before a log entry is encoded. The hook function
// is given the entry and the total set of fields to be logged. The set of fields which are
// returned are then logged. Hook functions can return a modified set of fields, or just return
// the unaltered fields.
//
// The Entry is not modified. It is purely informational.
//
// If a hook returns an error, that error is logged, but the in-flight log entry
// will proceed with the original set of fields.
//
// These global hooks will be injected into all loggers owned by this factory. They will
// execute before any hooks installed in individual loggers.
func (r *Factory) Hooks(hooks ...HookFunc) {
r.Lock()
defer r.Unlock()
r.hooks = append(r.hooks, hooks...)
r.refreshLoggers()
}
// ClearHooks removes all hooks.
func (r *Factory) ClearHooks() {
r.Lock()
defer r.Unlock()
r.hooks = nil
r.refreshLoggers()
}
func parseConfigString(s string) map[string]interface{} {
if s == "" {
return nil
}
items := strings.Split(s, ",")
m := map[string]interface{}{}
for _, setting := range items {
parts := strings.Split(setting, "=")
switch len(parts) {
case 1:
name := parts[0]
if strings.HasPrefix(name, "-") {
m[name[1:]] = false
} else {
m[name] = true
}
case 2:
m[parts[0]] = parts[1]
}
}
return m
}
// LevelsString reconfigures the log level for all loggers. Calling it with
// an empty string will reset the default level to info, and reset all loggers
// to use the default level.
//
// The string can contain a list of directives, separated by commas. Directives
// can set the default log level, and can explicitly set the log level for individual
// loggers.
//
// # Directives
//
// - Default level: Use the `*` directive to set the default log level. Examples:
//
// - // set the default log level to debug
// -* // set the default log level to off
//
// If the `*` directive is omitted, the default log level will be set to info.
//
// - Logger level: Use the name of the logger to set the log level for a specific
// logger. Examples:
//
// http // set the http logger to debug
// -http // set the http logger to off
// http=INF // set the http logger to info
//
// Multiple directives can be included, separated by commas. Examples:
//
// http // set http logger to debug
// http,sql // set http and sql logger to debug
// *,-http,sql=INF // set the default level to debug, disable the http logger,
// // and set the sql logger to info
func (r *Factory) LevelsString(s string) error {
m := parseConfigString(s)
levelMap := map[string]Level{}
var errMsgs []string
for key, val := range m {
switch t := val.(type) {
case bool:
if t {
levelMap[key] = DebugLevel
} else {
levelMap[key] = OffLevel
}
case string:
l, err := levelForAbbr(t)
levelMap[key] = l
if err != nil {
errMsgs = append(errMsgs, err.Error())
}
}
}
// first, check default setting
if defaultLevel, found := levelMap["*"]; found {
r.SetDefaultLevel(defaultLevel)
delete(levelMap, "*")
} else {
r.SetDefaultLevel(InfoLevel)
}
r.Lock()
defer r.Unlock()
// iterate through the current level map first.
// Any existing loggers which aren't in the levels map
// get reset to the default level.
for name, info := range r.loggers {
if _, found := levelMap[name]; !found {
info.levelEnabler = r.defaultLevel
}
}
// iterate through the levels map and set the specific levels
for name, level := range levelMap {
r.setLevel(name, level)
}
if len(errMsgs) > 0 {
return merry.New("errors parsing config string: " + strings.Join(errMsgs, ", "))
}
r.refreshLoggers()
return nil
}
// Configure uses a serializable struct to configure most of the options.
// This is useful when fully configuring the logging from an env var or file.
//
// The zero value for Config will set defaults for a standard, production logger:
//
// See the Config docs for details on settings.
func (r *Factory) Configure(cfg Config) error {
r.SetDefaultLevel(cfg.DefaultLevel)
var encCfg *EncoderConfig
if cfg.EncoderConfig != nil {
encCfg = cfg.EncoderConfig
} else {
if cfg.Development {
encCfg = NewDevelopmentEncoderConfig()
} else {
encCfg = NewEncoderConfig()
}
}
// These *Caller properties *must* be set or errors
// will occur
if encCfg.EncodeCaller == nil {
encCfg.EncodeCaller = zapcore.ShortCallerEncoder
}
if encCfg.EncodeLevel == nil {
encCfg.EncodeLevel = AbbrLevelEncoder
}
var encoder zapcore.Encoder
switch cfg.Encoding {
case "json":
encoder = NewJSONEncoder(encCfg)
case "ltsv":
encoder = NewLTSVEncoder(encCfg)
case "term":
encoder = NewConsoleEncoder(encCfg)
case "term-color":
encoder = NewColorizedConsoleEncoder(encCfg, nil)
case "console":
encoder = zapcore.NewConsoleEncoder((zapcore.EncoderConfig)(*encCfg))
case "":
if cfg.Development {
encoder = NewColorizedConsoleEncoder(encCfg, nil)
} else {
encoder = NewJSONEncoder(encCfg)
}
default:
return merry.Errorf("%s is not a valid encoding, must be one of: json, ltsv, term, or term-color", cfg.Encoding)
}
var addCaller bool
if cfg.AddCaller != nil {
addCaller = *cfg.AddCaller
} else {
addCaller = cfg.Development
}
if cfg.Levels != "" {
if err := r.LevelsString(cfg.Levels); err != nil {
return err
}
}
r.Lock()
defer r.Unlock()
r.encoder = encoder
r.addCaller = addCaller
r.refreshLoggers()
return nil
}
func levelForAbbr(abbr string) (Level, error) {
switch strings.ToLower(abbr) {
case "off":
return OffLevel, nil
case "dbg", "debug", "", "all":
return DebugLevel, nil
case "inf", "info":
return InfoLevel, nil
case "err", "error":
return ErrorLevel, nil
default:
return InfoLevel, fmt.Errorf("%s not recognized level, defaulting to info", abbr)
}
}