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  }