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  }