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