github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/logger/buffer.go (about)

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package logger
     5  
     6  import (
     7  	"bufio"
     8  	"io"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/keybase/go-logging"
    13  )
    14  
    15  const loggingFrequency = 10 * time.Millisecond
    16  
    17  type BufferedLoggerConfig struct {
    18  	Frequency time.Duration
    19  	Size      int
    20  }
    21  
    22  type triggerableTimer struct {
    23  	C          chan struct{}
    24  	timer      *time.Timer
    25  	sentinelCh chan struct{}
    26  	shutdownCh chan struct{}
    27  }
    28  
    29  func newTriggerableTimer(d time.Duration) *triggerableTimer {
    30  	t := &triggerableTimer{
    31  		C:          make(chan struct{}, 1),
    32  		sentinelCh: make(chan struct{}, 1),
    33  		timer:      time.NewTimer(0),
    34  		shutdownCh: make(chan struct{}),
    35  	}
    36  	go func() {
    37  		for {
    38  			select {
    39  			case <-t.timer.C:
    40  				<-t.sentinelCh
    41  				t.C <- struct{}{}
    42  			case <-t.shutdownCh:
    43  				t.timer.Stop()
    44  				return
    45  			}
    46  		}
    47  	}()
    48  	return t
    49  }
    50  
    51  func (t *triggerableTimer) ResetIfStopped(d time.Duration) {
    52  	if d == 0 {
    53  		return
    54  	}
    55  	select {
    56  	case t.sentinelCh <- struct{}{}:
    57  		t.timer.Reset(d)
    58  	default:
    59  	}
    60  }
    61  
    62  func (t *triggerableTimer) Close() {
    63  	close(t.shutdownCh)
    64  }
    65  
    66  type autoFlushingBufferedWriter struct {
    67  	lock           sync.RWMutex
    68  	bufferedWriter *bufio.Writer
    69  	backupWriter   *bufio.Writer
    70  
    71  	frequency    time.Duration
    72  	timer        *triggerableTimer
    73  	shutdown     chan struct{}
    74  	doneShutdown chan struct{}
    75  }
    76  
    77  var _ io.Writer = &autoFlushingBufferedWriter{}
    78  
    79  func (writer *autoFlushingBufferedWriter) backgroundFlush() {
    80  	for {
    81  		select {
    82  		case <-writer.timer.C:
    83  			// Swap out active and backup writers
    84  			writer.lock.Lock()
    85  			writer.bufferedWriter, writer.backupWriter = writer.
    86  				backupWriter, writer.bufferedWriter
    87  			writer.lock.Unlock()
    88  
    89  			writer.backupWriter.Flush()
    90  		case <-writer.shutdown:
    91  			writer.timer.shutdownCh <- struct{}{}
    92  			writer.bufferedWriter.Flush()
    93  			// If anyone is listening, notify them that we are done shutting down.
    94  			select {
    95  			case writer.doneShutdown <- struct{}{}:
    96  			default:
    97  			}
    98  			return
    99  		}
   100  	}
   101  }
   102  
   103  func defaultBufferedLoggerConfig() *BufferedLoggerConfig {
   104  	return &BufferedLoggerConfig{
   105  		Frequency: loggingFrequency,
   106  		Size:      4096,
   107  	}
   108  }
   109  
   110  // NewAutoFlushingBufferedWriter returns an io.Writer that buffers its output
   111  // and flushes automatically after `flushFrequency`.
   112  func NewAutoFlushingBufferedWriter(baseWriter io.Writer,
   113  	config *BufferedLoggerConfig) (w io.Writer, shutdown chan struct{}, done chan struct{}) {
   114  	if config == nil {
   115  		config = defaultBufferedLoggerConfig()
   116  	}
   117  	result := &autoFlushingBufferedWriter{
   118  		bufferedWriter: bufio.NewWriterSize(baseWriter, config.Size),
   119  		backupWriter:   bufio.NewWriterSize(baseWriter, config.Size),
   120  		frequency:      config.Frequency,
   121  		timer:          newTriggerableTimer(config.Frequency),
   122  		shutdown:       make(chan struct{}),
   123  		doneShutdown:   make(chan struct{}),
   124  	}
   125  	go result.backgroundFlush()
   126  	return result, result.shutdown, result.doneShutdown
   127  }
   128  
   129  func (writer *autoFlushingBufferedWriter) Write(p []byte) (int, error) {
   130  	// The locked resource here, the pointer bufferedWriter, is only being read
   131  	// even though this function is Write.
   132  	writer.lock.RLock()
   133  	defer writer.lock.RUnlock()
   134  
   135  	n, err := writer.bufferedWriter.Write(p)
   136  	if err != nil {
   137  		return n, err
   138  	}
   139  	writer.timer.ResetIfStopped(writer.frequency)
   140  
   141  	return n, nil
   142  }
   143  
   144  // EnableBufferedLogging turns on buffered logging - this is a performance
   145  // boost at the expense of not getting all the logs in a crash.
   146  func EnableBufferedLogging() {
   147  	writer, shutdown, done := NewAutoFlushingBufferedWriter(ErrorWriter(), nil)
   148  	stdErrLoggingShutdown = shutdown
   149  	stdErrLoggingShutdownDone = done
   150  	logBackend := logging.NewLogBackend(writer, "", 0)
   151  	logging.SetBackend(logBackend)
   152  }
   153  
   154  // Shutdown shuts down logger, flushing remaining logs if a backend with
   155  // buffering is used.
   156  func Shutdown() {
   157  	select {
   158  	case stdErrLoggingShutdown <- struct{}{}:
   159  		// Wait till logger is done
   160  		<-stdErrLoggingShutdownDone
   161  	default:
   162  	}
   163  }