package log import ( "fmt" golog "log" "os" "runtime" "strings" "sync" ) // Level indicates the level a log should be classified at type Level int // The possible levels a log can have const ( LevelDebug Level = iota + 1 LevelInfo LevelWarn LevelError ) /**** Color code from https://gist.github.com/ik5/d8ecde700972d4378d87 ****/ var ( Debug = Teal Info = Green Warn = Yellow Error = Red ) var ( Black = Color("\033[1;30m%s\033[0m") Red = Color("\033[1;31m%s\033[0m") Green = Color("\033[1;32m%s\033[0m") Yellow = Color("\033[1;33m%s\033[0m") Purple = Color("\033[1;34m%s\033[0m") Magenta = Color("\033[1;35m%s\033[0m") Teal = Color("\033[1;36m%s\033[0m") White = Color("\033[1;37m%s\033[0m") ) func Color(colorString string) func(...interface{}) string { sprint := func(args ...interface{}) string { return fmt.Sprintf(colorString, fmt.Sprint(args...)) } return sprint } /**** End color gist ****/ var levelString = map[Level]string{LevelDebug: "[DBUG]", LevelInfo: "[INFO]", LevelWarn: "[WARN]", LevelError: "[ERR ]"} var levelColor = map[Level]func(...interface{})string {LevelDebug: Debug, LevelInfo: Info, LevelWarn: Warn, LevelError: Error} // Logger is a go Logger wrapper that adds log levels and pattern filtering // It allows high level 'log level' filtering broadly // It allows allows two addition levels of filtering: // everythingFrom which allows inclusion of packages/patterns along with general logging // nothingExcept which allows focusing on just listed areas // privacyFilters by default apply all functions to printf args and add warnings to them for matches // privacyFilterReplace obscures any matches from the privacy filter functions type Logger struct { lock sync.Mutex // ensures atomic writes; protects the following fields logger *golog.Logger level Level useColor bool nothingExceptPatterns []string everythingFromPatterns []string excludeFromPatterns []string privacyFilers []func(string)bool privacyFilterReplace bool } // New returns a new Logger with a filter set to the supplied level func New(level Level) *Logger { return &Logger{logger: golog.New(os.Stderr, "", golog.Ldate|golog.Ltime), level: level, useColor: true, everythingFromPatterns: make([]string, 0), nothingExceptPatterns: make([]string, 0), privacyFilers: make([]func(string)bool, 0), privacyFilterReplace: false} } // NewFile returns a new Logger that logs to the supplied file with a filter set to the supplied level func NewFile(level Level, filename string) (*Logger, error) { logfile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return nil, err } return &Logger{logger: golog.New(logfile, "", golog.Ldate|golog.Ltime), level: level, useColor: false, everythingFromPatterns: make([]string, 0), nothingExceptPatterns: make([]string, 0), privacyFilers: make([]func(string)bool, 0), privacyFilterReplace: false}, nil } var stdLock sync.Mutex var std = New(LevelWarn) // SetStd sets the default logger all other functions use func SetStd(logger *Logger) { stdLock.Lock() defer stdLock.Unlock() std = logger } // filter func (l *Logger) filter(level Level) bool { _, file, _, ok := runtime.Caller(3) if !ok { file = "???" } for _, pattern := range l.excludeFromPatterns { if strings.Contains(file, pattern) { return false } } for _, pattern := range l.everythingFromPatterns { if strings.Contains(file, pattern) { return true } } for _, pattern := range l.nothingExceptPatterns { if strings.Contains(file, pattern) { return true } } if len(l.nothingExceptPatterns) > 0 { return false } return l.aboveLevel(level) } func (l *Logger) aboveLevel(level Level) bool { return level >= l.level } // SetLevel adjusts the output filter by logging level func (l *Logger) SetLevel(level Level) { l.lock.Lock() defer l.lock.Unlock() l.level = level } // SetUseColor toggles weather color output is used func (l *Logger) SetUseColor(useColor bool) { l.lock.Lock() defer l.lock.Unlock() l.useColor = useColor } // AddNothingExceptFilter enables strong filtering showing logs only for things on the approved list, adding this pattern to the list func (l *Logger) AddNothingExceptFilter(pattern string) { l.lock.Lock() defer l.lock.Unlock() l.nothingExceptPatterns = append(l.nothingExceptPatterns, pattern) } // AddEverythingFromPattern adds a pattern to skip log level filtering, guaranteeing all logs matching the pattern are seen func (l *Logger) AddEverythingFromPattern(pattern string) { l.lock.Lock() defer l.lock.Unlock() l.everythingFromPatterns = append(l.everythingFromPatterns, pattern) } // ExcludeFromPattern adds a pattern to exclude logs from func (l *Logger) ExcludeFromPattern(pattern string) { l.lock.Lock() defer l.lock.Unlock() l.excludeFromPatterns = append(l.excludeFromPatterns, pattern) } func (l *Logger) AddPrivacyFilter(fn func(string)bool) { l.lock.Lock() defer l.lock.Unlock() l.privacyFilers = append(l.privacyFilers, fn) } func (l *Logger) SetPrivacyFilterReplace(b bool) { l.lock.Lock() defer l.lock.Unlock() l.privacyFilterReplace = b } func (l *Logger) header(level Level) string { _, file, _, ok := runtime.Caller(3) if !ok { file = "???" } else { short := file count := 0 for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] count++ if count == 2 { break } } } file = short } if l.useColor { return file + " " + levelColor[level](levelString[level]) + " " } else { return file + " " + levelString[level] + " " } } func (l *Logger) privacyFilter(v ...interface{}) []interface{} { if len(l.privacyFilers) > 0 { for i, val := range v { for _, fn := range l.privacyFilers { switch cval := val.(type) { case string: if fn(cval) { if l.privacyFilterReplace { cval = strings.Repeat("*", len(cval)) } cval = "PRIVACY-FILTER-FLAGGED[" + cval + "]" if l.useColor { cval = Magenta(cval) } v[i] = cval } } } } } return v } // Printf outputs the format and variables, assuming it passes the filter levels func (l *Logger) Printf(level Level, format string, v ...interface{}) { l.lock.Lock() defer l.lock.Unlock() if l.filter(level) { l.logger.Output(3, l.header(level)+fmt.Sprintf(format, l.privacyFilter(v...)...)) } } // Println outputs the variables assuming the filter levels are passed func (l *Logger) Println(level Level, v ...interface{}) { l.lock.Lock() defer l.lock.Unlock() if l.filter(level) { l.logger.Output(3, l.header(level)+fmt.Sprintln(v...)) } } // SetLevel changes the filtering level of the system logger func SetLevel(level Level) { std.SetLevel(level) } // SetUseColor toggles weather color output is used func SetUseColor(useColor bool) { std.useColor = useColor } // AddNothingExceptFilter enables strong filtering showing logs only for things on the approved list, adding this pattern to the list func AddNothingExceptFilter(pattern string) { std.AddNothingExceptFilter(pattern) } // AddEverythingFromPattern adds a pattern to skip log level filtering, guaranteeing all logs matching the pattern are seen func AddEverythingFromPattern(pattern string) { std.AddEverythingFromPattern(pattern) } // ExcludeFromPattern adds a pattern to exclude logs from func ExcludeFromPattern(pattern string) { std.ExcludeFromPattern(pattern) } // AddPrivacyFilter adds a function applied to printf argument strings that can flag them as sensitive data func AddPrivacyFilter(fn func(string)bool) { std.AddPrivacyFilter(fn) } // SetPrivacyFilterReplace replaces the string argument to printf that a Privacy Filter identified with ****s func SetPrivacyFilterReplace(b bool) { std.SetPrivacyFilterReplace(b) } // Printf outputs the format with variables assuming it passes the filter level func Printf(level Level, format string, v ...interface{}) { std.Printf(level, format, v...) } // Println outputs the varibles assuming they pass the filter level func Println(level Level, v ...interface{}) { std.Println(level, v...) } // Debugf outputs the format and variables at the Debug level func Debugf(format string, v ...interface{}) { std.Printf(LevelDebug, format, v...) } // Infof outputs the format and variables at the Info level func Infof(format string, v ...interface{}) { std.Printf(LevelInfo, format, v...) } // Warnf outputs the format and variables at the Warning level func Warnf(format string, v ...interface{}) { std.Printf(LevelWarn, format, v...) } // Errorf outputs the format and variables at the Error level func Errorf(format string, v ...interface{}) { std.Printf(LevelError, format, v...) } // Debugln outputs the variables at the Debug level func Debugln(v ...interface{}) { std.Println(LevelDebug, v...) } // Infoln outputs the variables at the Info level func Infoln(v ...interface{}) { std.Println(LevelInfo, v...) } // Warnln outputs the variables at the Warn level func Warnln(v ...interface{}) { std.Println(LevelWarn, v...) } // Errorln outputs the variables at the Error level func Errorln(v ...interface{}) { std.Println(LevelError, v...) }