github.com/safing/portbase@v0.19.5/log/output.go (about) 1 package log 2 3 import ( 4 "fmt" 5 "os" 6 "runtime/debug" 7 "sync" 8 "time" 9 ) 10 11 type ( 12 // Adapter is used to write logs. 13 Adapter interface { 14 // Write is called for each log message. 15 Write(msg Message, duplicates uint64) 16 } 17 18 // AdapterFunc is a convenience type for implementing 19 // Adapter. 20 AdapterFunc func(msg Message, duplicates uint64) 21 22 // FormatFunc formats msg into a string. 23 FormatFunc func(msg Message, duplicates uint64) string 24 25 // SimpleFileAdapter implements Adapter and writes all 26 // messages to File. 27 SimpleFileAdapter struct { 28 Format FormatFunc 29 File *os.File 30 } 31 ) 32 33 var ( 34 // StdoutAdapter is a simple file adapter that writes 35 // all logs to os.Stdout using a predefined format. 36 StdoutAdapter = &SimpleFileAdapter{ 37 File: os.Stdout, 38 Format: defaultColorFormater, 39 } 40 41 // StderrAdapter is a simple file adapter that writes 42 // all logs to os.Stdout using a predefined format. 43 StderrAdapter = &SimpleFileAdapter{ 44 File: os.Stderr, 45 Format: defaultColorFormater, 46 } 47 ) 48 49 var ( 50 adapter Adapter = StdoutAdapter 51 52 schedulingEnabled = false 53 writeTrigger = make(chan struct{}) 54 ) 55 56 // SetAdapter configures the logging adapter to use. 57 // This must be called before the log package is initialized. 58 func SetAdapter(a Adapter) { 59 if initializing.IsSet() || a == nil { 60 return 61 } 62 63 adapter = a 64 } 65 66 // Write implements Adapter and calls fn. 67 func (fn AdapterFunc) Write(msg Message, duplicates uint64) { 68 fn(msg, duplicates) 69 } 70 71 // Write implements Adapter and writes msg the underlying file. 72 func (fileAdapter *SimpleFileAdapter) Write(msg Message, duplicates uint64) { 73 fmt.Fprintln(fileAdapter.File, fileAdapter.Format(msg, duplicates)) 74 } 75 76 // EnableScheduling enables external scheduling of the logger. This will require to manually trigger writes via TriggerWrite whenevery logs should be written. Please note that full buffers will also trigger writing. Must be called before Start() to have an effect. 77 func EnableScheduling() { 78 if !initializing.IsSet() { 79 schedulingEnabled = true 80 } 81 } 82 83 // TriggerWriter triggers log output writing. 84 func TriggerWriter() { 85 if started.IsSet() && schedulingEnabled { 86 select { 87 case writeTrigger <- struct{}{}: 88 default: 89 } 90 } 91 } 92 93 // TriggerWriterChannel returns the channel to trigger log writing. Returned channel will close if EnableScheduling() is not called correctly. 94 func TriggerWriterChannel() chan struct{} { 95 return writeTrigger 96 } 97 98 func defaultColorFormater(line Message, duplicates uint64) string { 99 return formatLine(line.(*logLine), duplicates, true) //nolint:forcetypeassert // TODO: improve 100 } 101 102 func startWriter() { 103 fmt.Printf("%s%s %s BOF%s\n", InfoLevel.color(), time.Now().Format(timeFormat), rightArrow, endColor()) 104 105 shutdownWaitGroup.Add(1) 106 go writerManager() 107 } 108 109 func writerManager() { 110 defer shutdownWaitGroup.Done() 111 112 for { 113 err := writer() 114 if err != nil { 115 Errorf("log: writer failed: %s", err) 116 } else { 117 return 118 } 119 } 120 } 121 122 func writer() (err error) { 123 defer func() { 124 // recover from panic 125 panicVal := recover() 126 if panicVal != nil { 127 err = fmt.Errorf("%s", panicVal) 128 129 // write stack to stderr 130 fmt.Fprintf( 131 os.Stderr, 132 `===== Error Report ===== 133 Message: %s 134 StackTrace: 135 136 %s 137 ===== End of Report ===== 138 `, 139 err, 140 string(debug.Stack()), 141 ) 142 } 143 }() 144 145 var currentLine *logLine 146 var duplicates uint64 147 148 for { 149 // reset 150 currentLine = nil 151 duplicates = 0 152 153 // wait until logs need to be processed 154 select { 155 case <-logsWaiting: // normal process 156 logsWaitingFlag.UnSet() 157 case <-forceEmptyingOfBuffer: // log buffer is full! 158 case <-shutdownSignal: // shutting down 159 finalizeWriting() 160 return 161 } 162 163 // wait for timeslot to log 164 select { 165 case <-writeTrigger: // normal process 166 case <-forceEmptyingOfBuffer: // log buffer is full! 167 case <-shutdownSignal: // shutting down 168 finalizeWriting() 169 return 170 } 171 172 // write all the logs! 173 writeLoop: 174 for { 175 select { 176 case nextLine := <-logBuffer: 177 // first line we process, just assign to currentLine 178 if currentLine == nil { 179 currentLine = nextLine 180 continue writeLoop 181 } 182 183 // we now have currentLine and nextLine 184 185 // if currentLine and nextLine are equal, do not print, just increase counter and continue 186 if nextLine.Equal(currentLine) { 187 duplicates++ 188 continue writeLoop 189 } 190 191 // if currentLine and line are _not_ equal, output currentLine 192 adapter.Write(currentLine, duplicates) 193 // add to unexpected logs 194 addUnexpectedLogs(currentLine) 195 // reset duplicate counter 196 duplicates = 0 197 // set new currentLine 198 currentLine = nextLine 199 default: 200 break writeLoop 201 } 202 } 203 204 // write final line 205 if currentLine != nil { 206 adapter.Write(currentLine, duplicates) 207 // add to unexpected logs 208 addUnexpectedLogs(currentLine) 209 } 210 211 // back down a little 212 select { 213 case <-time.After(10 * time.Millisecond): 214 case <-shutdownSignal: 215 finalizeWriting() 216 return 217 } 218 219 } 220 } 221 222 func finalizeWriting() { 223 for { 224 select { 225 case line := <-logBuffer: 226 adapter.Write(line, 0) 227 case <-time.After(10 * time.Millisecond): 228 fmt.Printf("%s%s %s EOF%s\n", InfoLevel.color(), time.Now().Format(timeFormat), leftArrow, endColor()) 229 return 230 } 231 } 232 } 233 234 // Last Unexpected Logs 235 236 var ( 237 lastUnexpectedLogs [10]string 238 lastUnexpectedLogsIndex int 239 lastUnexpectedLogsLock sync.Mutex 240 ) 241 242 func addUnexpectedLogs(line *logLine) { 243 // Add main line. 244 if line.level >= WarningLevel { 245 addUnexpectedLogLine(line) 246 return 247 } 248 249 // Check for unexpected lines in the tracer. 250 if line.tracer != nil { 251 for _, traceLine := range line.tracer.logs { 252 if traceLine.level >= WarningLevel { 253 // Add full trace. 254 addUnexpectedLogLine(line) 255 return 256 } 257 } 258 } 259 } 260 261 func addUnexpectedLogLine(line *logLine) { 262 lastUnexpectedLogsLock.Lock() 263 defer lastUnexpectedLogsLock.Unlock() 264 265 // Format line and add to logs. 266 lastUnexpectedLogs[lastUnexpectedLogsIndex] = formatLine(line, 0, false) 267 268 // Increase index and wrap back to start. 269 lastUnexpectedLogsIndex = (lastUnexpectedLogsIndex + 1) % len(lastUnexpectedLogs) 270 } 271 272 // GetLastUnexpectedLogs returns the last 10 log lines of level Warning an up. 273 func GetLastUnexpectedLogs() []string { 274 lastUnexpectedLogsLock.Lock() 275 defer lastUnexpectedLogsLock.Unlock() 276 277 // Make a copy and return. 278 logsLen := len(lastUnexpectedLogs) 279 start := lastUnexpectedLogsIndex 280 logsCopy := make([]string, 0, logsLen) 281 // Loop from mid-to-mid. 282 for i := start; i < start+logsLen; i++ { 283 if lastUnexpectedLogs[i%logsLen] != "" { 284 logsCopy = append(logsCopy, lastUnexpectedLogs[i%logsLen]) 285 } 286 } 287 288 return logsCopy 289 }