package log import ( "fmt" "os" "strings" "sync" "sync/atomic" "time" "github.com/tevino/abool" ) // concept /* - Logging function: - check if file-based levelling enabled - if yes, check if level is active on this file - check if level is active - send data to backend via big buffered channel - Backend: - wait until there is time for writing logs - write logs - configurable if logged to folder (buffer + rollingFileAppender) and/or console - console: log everything above INFO to stderr - Channel overbuffering protection: - if buffer is full, trigger write - Anti-Importing-Loop: - everything imports logging - logging is configured by main module and is supplied access to configuration and taskmanager */ // Severity describes a log level. type Severity uint32 type logLine struct { msg string tracer *ContextTracer level Severity timestamp time.Time file string line int } func (ll *logLine) Equal(ol *logLine) bool { switch { case ll.msg != ol.msg: return false case ll.tracer != nil || ol.tracer != nil: return false case ll.file != ol.file: return false case ll.line != ol.line: return false case ll.level != ol.level: return false } return true } // Log Levels const ( TraceLevel Severity = 1 DebugLevel Severity = 2 InfoLevel Severity = 3 WarningLevel Severity = 4 ErrorLevel Severity = 5 CriticalLevel Severity = 6 ) var ( logBuffer chan *logLine forceEmptyingOfBuffer = make(chan struct{}) logLevelInt = uint32(3) logLevel = &logLevelInt pkgLevelsActive = abool.NewBool(false) pkgLevels = make(map[string]Severity) pkgLevelsLock sync.Mutex logsWaiting = make(chan struct{}, 4) logsWaitingFlag = abool.NewBool(false) shutdownSignal = make(chan struct{}) shutdownWaitGroup sync.WaitGroup initializing = abool.NewBool(false) started = abool.NewBool(false) startedSignal = make(chan struct{}) ) // SetPkgLevels sets individual log levels for packages. func SetPkgLevels(levels map[string]Severity) { pkgLevelsLock.Lock() pkgLevels = levels pkgLevelsLock.Unlock() pkgLevelsActive.Set() } // UnSetPkgLevels removes all individual log levels for packages. func UnSetPkgLevels() { pkgLevelsActive.UnSet() } // SetLogLevel sets a new log level. func SetLogLevel(level Severity) { atomic.StoreUint32(logLevel, uint32(level)) } // ParseLevel returns the level severity of a log level name. func ParseLevel(level string) Severity { switch strings.ToLower(level) { case "trace": return 1 case "debug": return 2 case "info": return 3 case "warning": return 4 case "error": return 5 case "critical": return 6 } return 0 } // Start starts the logging system. Must be called in order to see logs. func Start() (err error) { if !initializing.SetToIf(false, true) { return nil } logBuffer = make(chan *logLine, 1024) initialLogLevel := ParseLevel(logLevelFlag) if initialLogLevel > 0 { atomic.StoreUint32(logLevel, uint32(initialLogLevel)) } else { err = fmt.Errorf("log warning: invalid log level \"%s\", falling back to level info", logLevelFlag) fmt.Fprintf(os.Stderr, "%s\n", err.Error()) } // get and set file loglevels pkgLogLevels := pkgLogLevelsFlag if len(pkgLogLevels) > 0 { newPkgLevels := make(map[string]Severity) for _, pair := range strings.Split(pkgLogLevels, ",") { splitted := strings.Split(pair, "=") if len(splitted) != 2 { err = fmt.Errorf("log warning: invalid file log level \"%s\", ignoring", pair) fmt.Fprintf(os.Stderr, "%s\n", err.Error()) break } fileLevel := ParseLevel(splitted[1]) if fileLevel == 0 { err = fmt.Errorf("log warning: invalid file log level \"%s\", ignoring", pair) fmt.Fprintf(os.Stderr, "%s\n", err.Error()) break } newPkgLevels[splitted[0]] = fileLevel } SetPkgLevels(newPkgLevels) } if !schedulingEnabled { close(writeTrigger) } startWriter() started.Set() close(startedSignal) return err } // Shutdown writes remaining log lines and then stops the log system. func Shutdown() { close(shutdownSignal) shutdownWaitGroup.Wait() }