github.com/safing/portbase@v0.19.5/log/logging.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/tevino/abool"
    12  )
    13  
    14  // concept
    15  /*
    16  - Logging function:
    17    - check if file-based levelling enabled
    18      - if yes, check if level is active on this file
    19    - check if level is active
    20    - send data to backend via big buffered channel
    21  - Backend:
    22    - wait until there is time for writing logs
    23    - write logs
    24    - configurable if logged to folder (buffer + rollingFileAppender) and/or console
    25    - console: log everything above INFO to stderr
    26  - Channel overbuffering protection:
    27    - if buffer is full, trigger write
    28  - Anti-Importing-Loop:
    29    - everything imports logging
    30    - logging is configured by main module and is supplied access to configuration and taskmanager
    31  */
    32  
    33  // Severity describes a log level.
    34  type Severity uint32
    35  
    36  // Message describes a log level message and is implemented
    37  // by logLine.
    38  type Message interface {
    39  	Text() string
    40  	Severity() Severity
    41  	Time() time.Time
    42  	File() string
    43  	LineNumber() int
    44  }
    45  
    46  type logLine struct {
    47  	msg       string
    48  	tracer    *ContextTracer
    49  	level     Severity
    50  	timestamp time.Time
    51  	file      string
    52  	line      int
    53  }
    54  
    55  func (ll *logLine) Text() string {
    56  	return ll.msg
    57  }
    58  
    59  func (ll *logLine) Severity() Severity {
    60  	return ll.level
    61  }
    62  
    63  func (ll *logLine) Time() time.Time {
    64  	return ll.timestamp
    65  }
    66  
    67  func (ll *logLine) File() string {
    68  	return ll.file
    69  }
    70  
    71  func (ll *logLine) LineNumber() int {
    72  	return ll.line
    73  }
    74  
    75  func (ll *logLine) Equal(ol *logLine) bool {
    76  	switch {
    77  	case ll.msg != ol.msg:
    78  		return false
    79  	case ll.tracer != nil || ol.tracer != nil:
    80  		return false
    81  	case ll.file != ol.file:
    82  		return false
    83  	case ll.line != ol.line:
    84  		return false
    85  	case ll.level != ol.level:
    86  		return false
    87  	}
    88  	return true
    89  }
    90  
    91  // Log Levels.
    92  const (
    93  	TraceLevel    Severity = 1
    94  	DebugLevel    Severity = 2
    95  	InfoLevel     Severity = 3
    96  	WarningLevel  Severity = 4
    97  	ErrorLevel    Severity = 5
    98  	CriticalLevel Severity = 6
    99  )
   100  
   101  var (
   102  	logBuffer             chan *logLine
   103  	forceEmptyingOfBuffer = make(chan struct{})
   104  
   105  	logLevelInt = uint32(InfoLevel)
   106  	logLevel    = &logLevelInt
   107  
   108  	pkgLevelsActive = abool.NewBool(false)
   109  	pkgLevels       = make(map[string]Severity)
   110  	pkgLevelsLock   sync.Mutex
   111  
   112  	logsWaiting     = make(chan struct{}, 1)
   113  	logsWaitingFlag = abool.NewBool(false)
   114  
   115  	shutdownFlag      = abool.NewBool(false)
   116  	shutdownSignal    = make(chan struct{})
   117  	shutdownWaitGroup sync.WaitGroup
   118  
   119  	initializing  = abool.NewBool(false)
   120  	started       = abool.NewBool(false)
   121  	startedSignal = make(chan struct{})
   122  )
   123  
   124  // SetPkgLevels sets individual log levels for packages. Only effective after Start().
   125  func SetPkgLevels(levels map[string]Severity) {
   126  	pkgLevelsLock.Lock()
   127  	pkgLevels = levels
   128  	pkgLevelsLock.Unlock()
   129  	pkgLevelsActive.Set()
   130  }
   131  
   132  // UnSetPkgLevels removes all individual log levels for packages.
   133  func UnSetPkgLevels() {
   134  	pkgLevelsActive.UnSet()
   135  }
   136  
   137  // GetLogLevel returns the current log level.
   138  func GetLogLevel() Severity {
   139  	return Severity(atomic.LoadUint32(logLevel))
   140  }
   141  
   142  // SetLogLevel sets a new log level. Only effective after Start().
   143  func SetLogLevel(level Severity) {
   144  	atomic.StoreUint32(logLevel, uint32(level))
   145  }
   146  
   147  // Name returns the name of the log level.
   148  func (s Severity) Name() string {
   149  	switch s {
   150  	case TraceLevel:
   151  		return "trace"
   152  	case DebugLevel:
   153  		return "debug"
   154  	case InfoLevel:
   155  		return "info"
   156  	case WarningLevel:
   157  		return "warning"
   158  	case ErrorLevel:
   159  		return "error"
   160  	case CriticalLevel:
   161  		return "critical"
   162  	default:
   163  		return "none"
   164  	}
   165  }
   166  
   167  // ParseLevel returns the level severity of a log level name.
   168  func ParseLevel(level string) Severity {
   169  	switch strings.ToLower(level) {
   170  	case "trace":
   171  		return 1
   172  	case "debug":
   173  		return 2
   174  	case "info":
   175  		return 3
   176  	case "warning":
   177  		return 4
   178  	case "error":
   179  		return 5
   180  	case "critical":
   181  		return 6
   182  	}
   183  	return 0
   184  }
   185  
   186  // Start starts the logging system. Must be called in order to see logs.
   187  func Start() (err error) {
   188  	if !initializing.SetToIf(false, true) {
   189  		return nil
   190  	}
   191  
   192  	logBuffer = make(chan *logLine, 1024)
   193  
   194  	if logLevelFlag != "" {
   195  		initialLogLevel := ParseLevel(logLevelFlag)
   196  		if initialLogLevel == 0 {
   197  			fmt.Fprintf(os.Stderr, "log warning: invalid log level \"%s\", falling back to level info\n", logLevelFlag)
   198  			initialLogLevel = InfoLevel
   199  		}
   200  
   201  		SetLogLevel(initialLogLevel)
   202  	}
   203  
   204  	// get and set file loglevels
   205  	pkgLogLevels := pkgLogLevelsFlag
   206  	if len(pkgLogLevels) > 0 {
   207  		newPkgLevels := make(map[string]Severity)
   208  		for _, pair := range strings.Split(pkgLogLevels, ",") {
   209  			splitted := strings.Split(pair, "=")
   210  			if len(splitted) != 2 {
   211  				err = fmt.Errorf("log warning: invalid file log level \"%s\", ignoring", pair)
   212  				fmt.Fprintf(os.Stderr, "%s\n", err.Error())
   213  				break
   214  			}
   215  			fileLevel := ParseLevel(splitted[1])
   216  			if fileLevel == 0 {
   217  				err = fmt.Errorf("log warning: invalid file log level \"%s\", ignoring", pair)
   218  				fmt.Fprintf(os.Stderr, "%s\n", err.Error())
   219  				break
   220  			}
   221  			newPkgLevels[splitted[0]] = fileLevel
   222  		}
   223  		SetPkgLevels(newPkgLevels)
   224  	}
   225  
   226  	if !schedulingEnabled {
   227  		close(writeTrigger)
   228  	}
   229  	startWriter()
   230  
   231  	started.Set()
   232  	close(startedSignal)
   233  
   234  	return err
   235  }
   236  
   237  // Shutdown writes remaining log lines and then stops the log system.
   238  func Shutdown() {
   239  	if shutdownFlag.SetToIf(false, true) {
   240  		close(shutdownSignal)
   241  	}
   242  	shutdownWaitGroup.Wait()
   243  }