github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/logsender/bufferedlogwriter.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENSE file for details.
     3  
     4  package logsender
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/utils/deque"
    15  )
    16  
    17  // LogRecord represents a log message in an agent which is to be
    18  // transmitted to the JES.
    19  type LogRecord struct {
    20  	Time     time.Time
    21  	Module   string
    22  	Location string // e.g. "foo.go:42"
    23  	Level    loggo.Level
    24  	Message  string
    25  
    26  	// Number of messages dropped after this one due to buffer limit.
    27  	DroppedAfter int
    28  }
    29  
    30  // LogStats contains statistics on logging.
    31  type LogStats struct {
    32  	// Enqueued is the number of log messages enqueued.
    33  	Enqueued uint64
    34  
    35  	// Sent is the number of log messages sent.
    36  	Sent uint64
    37  
    38  	// Dropped is the number of log messages dropped from the queue.
    39  	Dropped uint64
    40  }
    41  
    42  // LogRecordCh defines the channel type used to send log message
    43  // structs within the unit and machine agents.
    44  type LogRecordCh chan *LogRecord
    45  
    46  const writerName = "buffered-logs"
    47  
    48  // InstallBufferedLogWriter creates and returns a new BufferedLogWriter,
    49  // registering it with Loggo.
    50  func InstallBufferedLogWriter(maxLen int) (*BufferedLogWriter, error) {
    51  	writer := NewBufferedLogWriter(maxLen)
    52  	err := loggo.RegisterWriter(writerName, writer)
    53  	if err != nil {
    54  		return nil, errors.Annotate(err, "failed to set up log buffering")
    55  	}
    56  	return writer, nil
    57  }
    58  
    59  // UninstallBufferedLogWriter removes the BufferedLogWriter previously
    60  // installed by InstallBufferedLogWriter and closes it.
    61  func UninstallBufferedLogWriter() error {
    62  	writer, err := loggo.RemoveWriter(writerName)
    63  	if err != nil {
    64  		return errors.Annotate(err, "failed to uninstall log buffering")
    65  	}
    66  	bufWriter, ok := writer.(*BufferedLogWriter)
    67  	if !ok {
    68  		return errors.New("unexpected writer installed as buffered log writer")
    69  	}
    70  	bufWriter.Close()
    71  	return nil
    72  }
    73  
    74  // BufferedLogWriter is a loggo.Writer which buffers log messages in
    75  // memory. These messages are retrieved by reading from the channel
    76  // returned by the Logs method.
    77  //
    78  // Up to maxLen log messages will be buffered. If this limit is
    79  // exceeded, the oldest records will be automatically discarded.
    80  type BufferedLogWriter struct {
    81  	maxLen int
    82  	in     LogRecordCh
    83  	out    LogRecordCh
    84  
    85  	mu    sync.Mutex
    86  	stats LogStats
    87  }
    88  
    89  // NewBufferedLogWriter returns a new BufferedLogWriter which will
    90  // cache up to maxLen log messages.
    91  func NewBufferedLogWriter(maxLen int) *BufferedLogWriter {
    92  	w := &BufferedLogWriter{
    93  		maxLen: maxLen,
    94  		in:     make(LogRecordCh),
    95  		out:    make(LogRecordCh),
    96  	}
    97  	go w.loop()
    98  	return w
    99  }
   100  
   101  func (w *BufferedLogWriter) loop() {
   102  	buffer := deque.New()
   103  	var outCh LogRecordCh // Output channel - set when there's something to send.
   104  	var outRec *LogRecord // Next LogRecord to send to the output channel.
   105  
   106  	for {
   107  		// If there's something in the buffer and there's nothing
   108  		// queued up to send, set up the next LogRecord to send.
   109  		if outCh == nil {
   110  			if item, haveItem := buffer.PopFront(); haveItem {
   111  				outRec = item.(*LogRecord)
   112  				outCh = w.out
   113  			}
   114  		}
   115  
   116  		select {
   117  		case inRec, ok := <-w.in:
   118  			if !ok {
   119  				// Input channel has been closed; finish up.
   120  				close(w.out)
   121  				return
   122  			}
   123  
   124  			buffer.PushBack(inRec)
   125  
   126  			w.mu.Lock()
   127  			w.stats.Enqueued++
   128  			if buffer.Len() > w.maxLen {
   129  				// The buffer has exceeded the limit - discard the
   130  				// next LogRecord from the front of the queue.
   131  				buffer.PopFront()
   132  				outRec.DroppedAfter++
   133  				w.stats.Dropped++
   134  			}
   135  			w.mu.Unlock()
   136  
   137  		case outCh <- outRec:
   138  			outCh = nil // Signal that send happened.
   139  
   140  			w.mu.Lock()
   141  			w.stats.Sent++
   142  			w.mu.Unlock()
   143  		}
   144  	}
   145  
   146  }
   147  
   148  // Write sends a new log message to the writer. This implements the loggo.Writer interface.
   149  func (w *BufferedLogWriter) Write(entry loggo.Entry) {
   150  	w.in <- &LogRecord{
   151  		Time:     entry.Timestamp,
   152  		Module:   entry.Module,
   153  		Location: fmt.Sprintf("%s:%d", filepath.Base(entry.Filename), entry.Line),
   154  		Level:    entry.Level,
   155  		Message:  entry.Message,
   156  	}
   157  }
   158  
   159  // Logs returns a channel which emits log messages that have been sent
   160  // to the BufferedLogWriter instance.
   161  func (w *BufferedLogWriter) Logs() LogRecordCh {
   162  	return w.out
   163  }
   164  
   165  // Capacity returns the capacity of the BufferedLogWriter.
   166  func (w *BufferedLogWriter) Capacity() int {
   167  	return w.maxLen
   168  }
   169  
   170  // Stats returns the current LogStats for this BufferedLogWriter.
   171  func (w *BufferedLogWriter) Stats() LogStats {
   172  	w.mu.Lock()
   173  	defer w.mu.Unlock()
   174  	return w.stats
   175  }
   176  
   177  // Close cleans up the BufferedLogWriter instance. The output channel
   178  // returned by the Logs method will be closed and any further Write
   179  // calls will panic.
   180  func (w *BufferedLogWriter) Close() {
   181  	close(w.in)
   182  }