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 }