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  }