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 }