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