- Add a check to return immediately if the write buffer is empty - Create a copy of new history data to ensure it is immutable - Update the `GetHistory` method to use the `any` type for the buffer interface - Add a test case to verify that the buffer remains unchanged even if the original message is modified after writing
97 lines
1.6 KiB
Go
97 lines
1.6 KiB
Go
package proxy
|
|
|
|
import (
|
|
"container/ring"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
type LogMonitor struct {
|
|
clients map[chan []byte]bool
|
|
mu sync.RWMutex
|
|
buffer *ring.Ring
|
|
bufferMu sync.RWMutex
|
|
|
|
// typically this can be os.Stdout
|
|
stdout io.Writer
|
|
}
|
|
|
|
func NewLogMonitor() *LogMonitor {
|
|
return NewLogMonitorWriter(os.Stdout)
|
|
}
|
|
|
|
func NewLogMonitorWriter(stdout io.Writer) *LogMonitor {
|
|
return &LogMonitor{
|
|
clients: make(map[chan []byte]bool),
|
|
buffer: ring.New(10 * 1024), // keep 10KB of buffered logs
|
|
stdout: stdout,
|
|
}
|
|
}
|
|
|
|
func (w *LogMonitor) Write(p []byte) (n int, err error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
n, err = w.stdout.Write(p)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
w.bufferMu.Lock()
|
|
bufferCopy := make([]byte, len(p))
|
|
copy(bufferCopy, p)
|
|
w.buffer.Value = bufferCopy
|
|
w.buffer = w.buffer.Next()
|
|
w.bufferMu.Unlock()
|
|
|
|
w.broadcast(p)
|
|
return n, nil
|
|
}
|
|
|
|
func (w *LogMonitor) GetHistory() []byte {
|
|
w.bufferMu.RLock()
|
|
defer w.bufferMu.RUnlock()
|
|
|
|
var history []byte
|
|
w.buffer.Do(func(p any) {
|
|
if p != nil {
|
|
if content, ok := p.([]byte); ok {
|
|
history = append(history, content...)
|
|
}
|
|
}
|
|
})
|
|
return history
|
|
}
|
|
|
|
func (w *LogMonitor) Subscribe() chan []byte {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
ch := make(chan []byte, 100)
|
|
w.clients[ch] = true
|
|
return ch
|
|
}
|
|
|
|
func (w *LogMonitor) Unsubscribe(ch chan []byte) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
delete(w.clients, ch)
|
|
close(ch)
|
|
}
|
|
|
|
func (w *LogMonitor) broadcast(msg []byte) {
|
|
w.mu.RLock()
|
|
defer w.mu.RUnlock()
|
|
|
|
for client := range w.clients {
|
|
select {
|
|
case client <- msg:
|
|
default:
|
|
// If client buffer is full, skip
|
|
}
|
|
}
|
|
}
|