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