github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/common/flogging/loggerlevels.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package flogging 8 9 import ( 10 "fmt" 11 "regexp" 12 "sort" 13 "strings" 14 "sync" 15 16 "github.com/pkg/errors" 17 "go.uber.org/zap/zapcore" 18 ) 19 20 // LoggerLevels tracks the logging level of named loggers. 21 type LoggerLevels struct { 22 mutex sync.RWMutex 23 levelCache map[string]zapcore.Level 24 specs map[string]zapcore.Level 25 defaultLevel zapcore.Level 26 minLevel zapcore.Level 27 } 28 29 // DefaultLevel returns the default logging level for loggers that do not have 30 // an explicit level set. 31 func (l *LoggerLevels) DefaultLevel() zapcore.Level { 32 l.mutex.RLock() 33 lvl := l.defaultLevel 34 l.mutex.RUnlock() 35 return lvl 36 } 37 38 // ActivateSpec is used to modify logging levels. 39 // 40 // The logging specification has the following form: 41 // [<logger>[,<logger>...]=]<level>[:[<logger>[,<logger>...]=]<level>...] 42 func (l *LoggerLevels) ActivateSpec(spec string) error { 43 l.mutex.Lock() 44 defer l.mutex.Unlock() 45 46 defaultLevel := zapcore.InfoLevel 47 specs := map[string]zapcore.Level{} 48 for _, field := range strings.Split(spec, ":") { 49 split := strings.Split(field, "=") 50 switch len(split) { 51 case 1: // level 52 if field != "" && !IsValidLevel(field) { 53 return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) 54 } 55 defaultLevel = NameToLevel(field) 56 57 case 2: // <logger>[,<logger>...]=<level> 58 if split[0] == "" { 59 return errors.Errorf("invalid logging specification '%s': no logger specified in segment '%s'", spec, field) 60 } 61 if field != "" && !IsValidLevel(split[1]) { 62 return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) 63 } 64 65 level := NameToLevel(split[1]) 66 loggers := strings.Split(split[0], ",") 67 for _, logger := range loggers { 68 // check if the logger name in the spec is valid. The 69 // trailing period is trimmed as logger names in specs 70 // ending with a period signifies that this part of the 71 // spec refers to the exact logger name (i.e. is not a prefix) 72 if !isValidLoggerName(strings.TrimSuffix(logger, ".")) { 73 return errors.Errorf("invalid logging specification '%s': bad logger name '%s'", spec, logger) 74 } 75 specs[logger] = level 76 } 77 78 default: 79 return errors.Errorf("invalid logging specification '%s': bad segment '%s'", spec, field) 80 } 81 } 82 83 minLevel := defaultLevel 84 for _, lvl := range specs { 85 if lvl < minLevel { 86 minLevel = lvl 87 } 88 } 89 90 l.minLevel = minLevel 91 l.defaultLevel = defaultLevel 92 l.specs = specs 93 l.levelCache = map[string]zapcore.Level{} 94 95 return nil 96 } 97 98 // logggerNameRegexp defines the valid logger names 99 var loggerNameRegexp = regexp.MustCompile(`^[[:alnum:]_#:-]+(\.[[:alnum:]_#:-]+)*$`) 100 101 // isValidLoggerName checks whether a logger name contains only valid 102 // characters. Names that begin/end with periods or contain special 103 // characters (other than periods, underscores, pound signs, colons 104 // and dashes) are invalid. 105 func isValidLoggerName(loggerName string) bool { 106 return loggerNameRegexp.MatchString(loggerName) 107 } 108 109 // Level returns the effective logging level for a logger. If a level has not 110 // been explicitly set for the logger, the default logging level will be 111 // returned. 112 func (l *LoggerLevels) Level(loggerName string) zapcore.Level { 113 if level, ok := l.cachedLevel(loggerName); ok { 114 return level 115 } 116 117 l.mutex.Lock() 118 level := l.calculateLevel(loggerName) 119 l.levelCache[loggerName] = level 120 l.mutex.Unlock() 121 122 return level 123 } 124 125 // calculateLevel walks the logger name back to find the appropriate 126 // log level from the current spec. 127 func (l *LoggerLevels) calculateLevel(loggerName string) zapcore.Level { 128 candidate := loggerName + "." 129 for { 130 if lvl, ok := l.specs[candidate]; ok { 131 return lvl 132 } 133 134 idx := strings.LastIndex(candidate, ".") 135 if idx <= 0 { 136 return l.defaultLevel 137 } 138 candidate = candidate[:idx] 139 } 140 } 141 142 // cachedLevel attempts to retrieve the effective log level for a logger from the 143 // cache. If the logger is not found, ok will be false. 144 func (l *LoggerLevels) cachedLevel(loggerName string) (lvl zapcore.Level, ok bool) { 145 l.mutex.RLock() 146 level, ok := l.levelCache[loggerName] 147 l.mutex.RUnlock() 148 return level, ok 149 } 150 151 // Spec returns a normalized version of the active logging spec. 152 func (l *LoggerLevels) Spec() string { 153 l.mutex.RLock() 154 defer l.mutex.RUnlock() 155 156 var fields []string 157 for k, v := range l.specs { 158 fields = append(fields, fmt.Sprintf("%s=%s", k, v)) 159 } 160 161 sort.Strings(fields) 162 fields = append(fields, l.defaultLevel.String()) 163 164 return strings.Join(fields, ":") 165 } 166 167 // Enabled function is an enabled check that evaluates the minimum active logging level. 168 // It serves as a fast check before the (relatively) expensive Check call in the core. 169 func (l *LoggerLevels) Enabled(lvl zapcore.Level) bool { 170 l.mutex.RLock() 171 enabled := l.minLevel.Enabled(lvl) 172 l.mutex.RUnlock() 173 return enabled 174 }