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 // Message describes a log level message and is implemented // by logLine. type Message interface { Text() string Severity() Severity Time() time.Time File() string LineNumber() int } type logLine struct { msg string tracer *ContextTracer level Severity timestamp time.Time file string line int } func (ll *logLine) Text() string { return ll.msg } func (ll *logLine) Severity() Severity { return ll.level } func (ll *logLine) Time() time.Time { return ll.timestamp } func (ll *logLine) File() string { return ll.file } func (ll *logLine) LineNumber() int { return ll.line } 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(InfoLevel) logLevel = &logLevelInt pkgLevelsActive = abool.NewBool(false) pkgLevels = make(map[string]Severity) pkgLevelsLock sync.Mutex logsWaiting = make(chan struct{}, 1) logsWaitingFlag = abool.NewBool(false) shutdownFlag = 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. Only effective after Start(). 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() } // GetLogLevel returns the current log level. func GetLogLevel() Severity { return Severity(atomic.LoadUint32(logLevel)) } // SetLogLevel sets a new log level. Only effective after Start(). func SetLogLevel(level Severity) { atomic.StoreUint32(logLevel, uint32(level)) } // Name returns the name of the log level. func (s Severity) Name() string { switch s { case TraceLevel: return "trace" case DebugLevel: return "debug" case InfoLevel: return "info" case WarningLevel: return "warning" case ErrorLevel: return "error" case CriticalLevel: return "critical" default: return "none" } } // 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) if logLevelFlag != "" { initialLogLevel := ParseLevel(logLevelFlag) if initialLogLevel == 0 { fmt.Fprintf(os.Stderr, "log warning: invalid log level \"%s\", falling back to level info\n", logLevelFlag) initialLogLevel = InfoLevel } SetLogLevel(initialLogLevel) } // 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() { if shutdownFlag.SetToIf(false, true) { close(shutdownSignal) } shutdownWaitGroup.Wait() }