github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/utils/deque"
    14  )
    15  
    16  // LogRecord represents a log message in an agent which is to be
    17  // transmitted to the JES.
    18  type LogRecord struct {
    19  	Time     time.Time
    20  	Module   string
    21  	Location string // e.g. "foo.go:42"
    22  	Level    loggo.Level
    23  	Message  string
    24  
    25  	// Number of messages dropped after this one due to buffer limit.
    26  	DroppedAfter int
    27  }
    28  
    29  // LogRecordCh defines the channel type used to send log message
    30  // structs within the unit and machine agents.
    31  type LogRecordCh chan *LogRecord
    32  
    33  const writerName = "buffered-logs"
    34  
    35  // InstallBufferedLogWriter creates a new BufferedLogWriter, registers
    36  // it with Loggo and returns its output channel.
    37  func InstallBufferedLogWriter(maxLen int) (LogRecordCh, error) {
    38  	writer := NewBufferedLogWriter(maxLen)
    39  	err := loggo.RegisterWriter(writerName, writer)
    40  	if err != nil {
    41  		return nil, errors.Annotate(err, "failed to set up log buffering")
    42  	}
    43  	return writer.Logs(), nil
    44  }
    45  
    46  // UninstallBufferedLogWriter removes the BufferedLogWriter previously
    47  // installed by InstallBufferedLogWriter and closes it.
    48  func UninstallBufferedLogWriter() error {
    49  	writer, err := loggo.RemoveWriter(writerName)
    50  	if err != nil {
    51  		return errors.Annotate(err, "failed to uninstall log buffering")
    52  	}
    53  	bufWriter, ok := writer.(*BufferedLogWriter)
    54  	if !ok {
    55  		return errors.New("unexpected writer installed as buffered log writer")
    56  	}
    57  	bufWriter.Close()
    58  	return nil
    59  }
    60  
    61  // BufferedLogWriter is a loggo.Writer which buffers log messages in
    62  // memory. These messages are retrieved by reading from the channel
    63  // returned by the Logs method.
    64  //
    65  // Up to maxLen log messages will be buffered. If this limit is
    66  // exceeded, the oldest records will be automatically discarded.
    67  type BufferedLogWriter struct {
    68  	maxLen int
    69  	in     LogRecordCh
    70  	out    LogRecordCh
    71  }
    72  
    73  // NewBufferedLogWriter returns a new BufferedLogWriter which will
    74  // cache up to maxLen log messages.
    75  func NewBufferedLogWriter(maxLen int) *BufferedLogWriter {
    76  	w := &BufferedLogWriter{
    77  		maxLen: maxLen,
    78  		in:     make(LogRecordCh),
    79  		out:    make(LogRecordCh),
    80  	}
    81  	go w.loop()
    82  	return w
    83  }
    84  
    85  func (w *BufferedLogWriter) loop() {
    86  	buffer := deque.New()
    87  	var outCh LogRecordCh // Output channel - set when there's something to send.
    88  	var outRec *LogRecord // Next LogRecord to send to the output channel.
    89  
    90  	for {
    91  		// If there's something in the buffer and there's nothing
    92  		// queued up to send, set up the next LogRecord to send.
    93  		if outCh == nil {
    94  			if item, haveItem := buffer.PopFront(); haveItem {
    95  				outRec = item.(*LogRecord)
    96  				outCh = w.out
    97  			}
    98  		}
    99  
   100  		select {
   101  		case inRec, ok := <-w.in:
   102  			if !ok {
   103  				// Input channel has been closed; finish up.
   104  				close(w.out)
   105  				return
   106  			}
   107  
   108  			buffer.PushBack(inRec)
   109  
   110  			if buffer.Len() > w.maxLen {
   111  				// The buffer has exceeded the limit - discard the
   112  				// next LogRecord from the front of the queue.
   113  				buffer.PopFront()
   114  				outRec.DroppedAfter++
   115  			}
   116  
   117  		case outCh <- outRec:
   118  			outCh = nil // Signal that send happened.
   119  		}
   120  	}
   121  
   122  }
   123  
   124  // Write sends a new log message to the writer. This implements the loggo.Writer interface.
   125  func (w *BufferedLogWriter) Write(entry loggo.Entry) {
   126  	w.in <- &LogRecord{
   127  		Time:     entry.Timestamp,
   128  		Module:   entry.Module,
   129  		Location: fmt.Sprintf("%s:%d", filepath.Base(entry.Filename), entry.Line),
   130  		Level:    entry.Level,
   131  		Message:  entry.Message,
   132  	}
   133  }
   134  
   135  // Logs returns a channel which emits log messages that have been sent
   136  // to the BufferedLogWriter instance.
   137  func (w *BufferedLogWriter) Logs() LogRecordCh {
   138  	return w.out
   139  }
   140  
   141  // Close cleans up the BufferedLogWriter instance. The output channel
   142  // returned by the Logs method will be closed and any further Write
   143  // calls will panic.
   144  func (w *BufferedLogWriter) Close() {
   145  	close(w.in)
   146  }