github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/logging/loggers/channel_logger.go (about) 1 // Copyright Monax Industries Limited 2 // SPDX-License-Identifier: Apache-2.0 3 4 package loggers 5 6 import ( 7 "sync" 8 9 "github.com/eapache/channels" 10 "github.com/go-kit/kit/log" 11 "github.com/hyperledger/burrow/logging/errors" 12 ) 13 14 const ( 15 DefaultLoggingRingBufferCap channels.BufferCap = 100 16 ) 17 18 type ChannelLogger struct { 19 ch channels.Channel 20 sync.RWMutex 21 } 22 23 var _ log.Logger = (*ChannelLogger)(nil) 24 25 // Creates a Logger that uses a uses a non-blocking ring buffered channel. 26 // This logger provides a common abstraction for both a buffered, flushable 27 // logging cache. And a non-blocking conduit to transmit logs via 28 // DrainForever (or NonBlockingLogger). 29 func NewChannelLogger(loggingRingBufferCap channels.BufferCap) *ChannelLogger { 30 return &ChannelLogger{ 31 ch: channels.NewRingChannel(loggingRingBufferCap), 32 } 33 } 34 35 func (cl *ChannelLogger) Log(keyvals ...interface{}) error { 36 // In case channel is being reset 37 cl.RLock() 38 defer cl.RUnlock() 39 cl.ch.In() <- keyvals 40 // We don't have a way to pass on any logging errors, but that's okay: Log is 41 // a maximal interface and the error return type is only there for special 42 // cases. 43 return nil 44 } 45 46 // Get the current occupancy level of the ring buffer 47 func (cl *ChannelLogger) BufferLength() int { 48 return cl.ch.Len() 49 } 50 51 // Get the cap off the internal ring buffer 52 func (cl *ChannelLogger) BufferCap() channels.BufferCap { 53 return cl.ch.Cap() 54 } 55 56 // Read a log line by waiting until one is available and returning it 57 func (cl *ChannelLogger) WaitReadLogLine() []interface{} { 58 logLine, ok := <-cl.ch.Out() 59 return readLogLine(logLine, ok) 60 } 61 62 // Tries to read a log line from the channel buffer or returns nil if none is 63 // immediately available 64 func (cl *ChannelLogger) ReadLogLine() []interface{} { 65 select { 66 case logLine, ok := <-cl.ch.Out(): 67 return readLogLine(logLine, ok) 68 default: 69 return nil 70 } 71 } 72 73 func readLogLine(logLine interface{}, ok bool) []interface{} { 74 if !ok { 75 // Channel closed 76 return nil 77 } 78 // We are passing slices of interfaces down this channel (go-kit log's Log 79 // interface type), a panic is the right thing to do if this type assertion 80 // fails. 81 return logLine.([]interface{}) 82 } 83 84 // Enters an infinite loop that will drain any log lines from the passed logger. 85 // You may pass in a channel 86 // 87 // Exits if the channel is closed. 88 func (cl *ChannelLogger) DrainForever(logger log.Logger, errCh channels.Channel) { 89 // logLine could be nil if channel was closed while waiting for next line 90 for logLine := cl.WaitReadLogLine(); logLine != nil; logLine = cl.WaitReadLogLine() { 91 err := logger.Log(logLine...) 92 if err != nil && errCh != nil { 93 errCh.In() <- err 94 } 95 } 96 } 97 98 // Drains everything that is available at the time of calling 99 func (cl *ChannelLogger) Flush(logger log.Logger) error { 100 // Grab the buffer at the here rather than within loop condition so that we 101 // do not drain the buffer forever 102 cl.Lock() 103 defer cl.Unlock() 104 bufferLength := cl.BufferLength() 105 var errs []error 106 for i := 0; i < bufferLength; i++ { 107 logLine := cl.WaitReadLogLine() 108 if logLine != nil { 109 err := logger.Log(logLine...) 110 if err != nil { 111 errs = append(errs, err) 112 } 113 } 114 } 115 return errors.CombineErrors(errs) 116 } 117 118 // Drains the next contiguous segment of loglines up to the buffer cap waiting 119 // for at least one line 120 func (cl *ChannelLogger) FlushLogLines() [][]interface{} { 121 logLines := make([][]interface{}, 0, cl.ch.Len()) 122 cl.Flush(log.LoggerFunc(func(keyvals ...interface{}) error { 123 logLines = append(logLines, keyvals) 124 return nil 125 })) 126 return logLines 127 } 128 129 // Close the existing channel halting goroutines that are draining the channel 130 // and create a new channel to buffer into. Should not cause any log lines 131 // arriving concurrently to be lost, but any that have not been drained from 132 // old channel may be. 133 func (cl *ChannelLogger) Reset() { 134 cl.RWMutex.Lock() 135 defer cl.RWMutex.Unlock() 136 cl.ch.Close() 137 cl.ch = channels.NewRingChannel(cl.ch.Cap()) 138 } 139 140 // Returns a Logger that wraps the outputLogger passed and does not block on 141 // calls to Log and a channel of any errors from the underlying logger 142 func NonBlockingLogger(outputLogger log.Logger) (*ChannelLogger, channels.Channel) { 143 cl := NewChannelLogger(DefaultLoggingRingBufferCap) 144 errCh := channels.NewRingChannel(cl.BufferCap()) 145 go cl.DrainForever(outputLogger, errCh) 146 return cl, errCh 147 }