-
Notifications
You must be signed in to change notification settings - Fork 1
/
loggerlevels.go
157 lines (131 loc) · 4.3 KB
/
loggerlevels.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
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package flogging
import (
"fmt"
"regexp"
"sort"
"strings"
"sync"
"github.com/pkg/errors"
"go.uber.org/zap/zapcore"
)
// LoggerLevels tracks the logging level of named loggers.
type LoggerLevels struct {
defaultLevel zapcore.Level
mutex sync.RWMutex
levelCache map[string]zapcore.Level
specs map[string]zapcore.Level
}
// DefaultLevel returns the default logging level for loggers that do not have
// an explicit level set.
func (l *LoggerLevels) DefaultLevel() zapcore.Level {
l.mutex.RLock()
lvl := l.defaultLevel
l.mutex.RUnlock()
return lvl
}
// ActivateSpec is used to modify logging levels.
//
// The logging specification has the following form:
// [<logger>[,<logger>...]=]<level>[:[<logger>[,<logger>...]=]<level>...]
func (l *LoggerLevels) ActivateSpec(spec string) error {
l.mutex.Lock()
defer l.mutex.Unlock()
defaultLevel := zapcore.InfoLevel
specs := map[string]zapcore.Level{}
for _, field := range strings.Split(spec, ":") {
split := strings.Split(field, "=")
switch len(split) {
case 1: // level
if field != "" && !IsValidLevel(field) {
return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field)
}
defaultLevel = NameToLevel(field)
case 2: // <logger>[,<logger>...]=<level>
if split[0] == "" {
return errors.Errorf("invalid logging specification '%s': no logger specified in segment '%s'", spec, field)
}
if field != "" && !IsValidLevel(split[1]) {
return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field)
}
level := NameToLevel(split[1])
loggers := strings.Split(split[0], ",")
for _, logger := range loggers {
// check if the logger name in the spec is valid. The
// trailing period is trimmed as logger names in specs
// ending with a period signifies that this part of the
// spec refers to the exact logger name (i.e. is not a prefix)
if !isValidLoggerName(strings.TrimSuffix(logger, ".")) {
return errors.Errorf("invalid logging specification '%s': bad logger name '%s'", spec, logger)
}
specs[logger] = level
}
default:
return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field)
}
}
l.defaultLevel = defaultLevel
l.specs = specs
l.levelCache = map[string]zapcore.Level{}
return nil
}
// logggerNameRegexp defines the valid logger names
var loggerNameRegexp = regexp.MustCompile(`^[[:alnum:]_#:-]+(\.[[:alnum:]_#:-]+)*$`)
// isValidLoggerName checks whether a logger name contains only valid
// characters. Names that begin/end with periods or contain special
// characters (other than periods, underscores, pound signs, colons
// and dashes) are invalid.
func isValidLoggerName(loggerName string) bool {
return loggerNameRegexp.MatchString(loggerName)
}
// Level returns the effective logging level for a logger. If a level has not
// been explicitly set for the logger, the default logging level will be
// returned.
func (l *LoggerLevels) Level(loggerName string) zapcore.Level {
if level, ok := l.cachedLevel(loggerName); ok {
return level
}
l.mutex.Lock()
level := l.calculateLevel(loggerName)
l.levelCache[loggerName] = level
l.mutex.Unlock()
return level
}
// calculateLevel walks the logger name back to find the appropriate
// log level from the current spec.
func (l *LoggerLevels) calculateLevel(loggerName string) zapcore.Level {
candidate := loggerName + "."
for {
if lvl, ok := l.specs[candidate]; ok {
return lvl
}
idx := strings.LastIndex(candidate, ".")
if idx <= 0 {
return l.defaultLevel
}
candidate = candidate[:idx]
}
}
// cachedLevel attempts to retrieve the effective log level for a logger from the
// cache. If the logger is not found, ok will be false.
func (l *LoggerLevels) cachedLevel(loggerName string) (lvl zapcore.Level, ok bool) {
l.mutex.RLock()
level, ok := l.levelCache[loggerName]
l.mutex.RUnlock()
return level, ok
}
// Spec returns a normalized version of the active logging spec.
func (l *LoggerLevels) Spec() string {
l.mutex.RLock()
defer l.mutex.RUnlock()
var fields []string
for k, v := range l.specs {
fields = append(fields, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(fields)
fields = append(fields, l.defaultLevel.String())
return strings.Join(fields, ":")
}