package log import "fmt" type level byte const ( levelDebug level = 1 << iota levelInfo levelError ) type filter struct { next Logger allowed level // XOR'd levels for default case initiallyAllowed level // XOR'd levels for initial case allowedKeyvals map[keyval]level // When key-value match, use this level } type keyval struct { key interface{} value interface{} } // NewFilter wraps next and implements filtering. See the commentary on the // Option functions for a detailed description of how to configure levels. If // no options are provided, all leveled log events created with Debug, Info or // Error helper methods are squelched. func NewFilter(next Logger, options ...Option) Logger { l := &filter{ next: next, allowedKeyvals: make(map[keyval]level), } for _, option := range options { option(l) } l.initiallyAllowed = l.allowed return l } func (l *filter) Info(msg string, keyvals ...interface{}) { levelAllowed := l.allowed&levelInfo != 0 if !levelAllowed { return } l.next.Info(msg, keyvals...) } func (l *filter) Debug(msg string, keyvals ...interface{}) { levelAllowed := l.allowed&levelDebug != 0 if !levelAllowed { return } l.next.Debug(msg, keyvals...) } func (l *filter) Error(msg string, keyvals ...interface{}) { levelAllowed := l.allowed&levelError != 0 if !levelAllowed { return } l.next.Error(msg, keyvals...) } // With implements Logger by constructing a new filter with a keyvals appended // to the logger. // // If custom level was set for a keyval pair using one of the // Allow*With methods, it is used as the logger's level. // // Examples: // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto")) // logger.With("module", "crypto").Info("Hello") # produces "I... Hello module=crypto" // // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto"), log.AllowNoneWith("user", "Sam")) // logger.With("module", "crypto", "user", "Sam").Info("Hello") # returns nil // // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto"), log.AllowNoneWith("user", "Sam")) // logger.With("user", "Sam").With("module", "crypto").Info("Hello") # produces "I... Hello module=crypto user=Sam" func (l *filter) With(keyvals ...interface{}) Logger { keyInAllowedKeyvals := false for i := len(keyvals) - 2; i >= 0; i -= 2 { for kv, allowed := range l.allowedKeyvals { if keyvals[i] == kv.key { keyInAllowedKeyvals = true // Example: // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto")) // logger.With("module", "crypto") if keyvals[i+1] == kv.value { return &filter{ next: l.next.With(keyvals...), allowed: allowed, // set the desired level allowedKeyvals: l.allowedKeyvals, initiallyAllowed: l.initiallyAllowed, } } } } } // Example: // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto")) // logger.With("module", "main") if keyInAllowedKeyvals { return &filter{ next: l.next.With(keyvals...), allowed: l.initiallyAllowed, // return back to initially allowed allowedKeyvals: l.allowedKeyvals, initiallyAllowed: l.initiallyAllowed, } } return &filter{ next: l.next.With(keyvals...), allowed: l.allowed, // simply continue with the current level allowedKeyvals: l.allowedKeyvals, initiallyAllowed: l.initiallyAllowed, } } //-------------------------------------------------------------------------------- // Option sets a parameter for the filter. type Option func(*filter) // AllowLevel returns an option for the given level or error if no option exist // for such level. func AllowLevel(lvl string) (Option, error) { switch lvl { case "debug": return AllowDebug(), nil case "info": return AllowInfo(), nil case "error": return AllowError(), nil case "none": return AllowNone(), nil default: return nil, fmt.Errorf("Expected either \"info\", \"debug\", \"error\" or \"none\" level, given %s", lvl) } } // AllowAll is an alias for AllowDebug. func AllowAll() Option { return AllowDebug() } // AllowDebug allows error, info and debug level log events to pass. func AllowDebug() Option { return allowed(levelError | levelInfo | levelDebug) } // AllowInfo allows error and info level log events to pass. func AllowInfo() Option { return allowed(levelError | levelInfo) } // AllowError allows only error level log events to pass. func AllowError() Option { return allowed(levelError) } // AllowNone allows no leveled log events to pass. func AllowNone() Option { return allowed(0) } func allowed(allowed level) Option { return func(l *filter) { l.allowed = allowed } } // AllowDebugWith allows error, info and debug level log events to pass for a specific key value pair. func AllowDebugWith(key interface{}, value interface{}) Option { return func(l *filter) { l.allowedKeyvals[keyval{key, value}] = levelError | levelInfo | levelDebug } } // AllowInfoWith allows error and info level log events to pass for a specific key value pair. func AllowInfoWith(key interface{}, value interface{}) Option { return func(l *filter) { l.allowedKeyvals[keyval{key, value}] = levelError | levelInfo } } // AllowErrorWith allows only error level log events to pass for a specific key value pair. func AllowErrorWith(key interface{}, value interface{}) Option { return func(l *filter) { l.allowedKeyvals[keyval{key, value}] = levelError } } // AllowNoneWith allows no leveled log events to pass for a specific key value pair. func AllowNoneWith(key interface{}, value interface{}) Option { return func(l *filter) { l.allowedKeyvals[keyval{key, value}] = 0 } }