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 }