safing-portbase/log/logging.go
2022-11-04 16:12:42 +01:00

243 lines
5.1 KiB
Go

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()
}