100 строки
2.1 KiB
Go
100 строки
2.1 KiB
Go
package webapp
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// LogBuffer is a small in-memory ring buffer the webapp uses to
|
|
// expose the last N worker log lines on the Logs page. The worker
|
|
// process feeds it via slog/JSON or by calling Append directly.
|
|
//
|
|
// Capacity is bounded; old entries are evicted FIFO.
|
|
type LogBuffer struct {
|
|
mu sync.RWMutex
|
|
buf []string
|
|
capN int
|
|
offset int
|
|
full bool
|
|
}
|
|
|
|
// NewLogBuffer returns an empty LogBuffer that holds up to capN
|
|
// lines. capN <= 0 falls back to a sensible default.
|
|
func NewLogBuffer(capN int) *LogBuffer {
|
|
if capN <= 0 {
|
|
capN = 5000
|
|
}
|
|
return &LogBuffer{buf: make([]string, 0, capN), capN: capN}
|
|
}
|
|
|
|
// Append adds a single line to the buffer. Newlines are stripped so
|
|
// multi-line log records do not split into separate rows.
|
|
func (l *LogBuffer) Append(line string) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
line = strings.TrimRight(line, "\n")
|
|
if line == "" {
|
|
return
|
|
}
|
|
if len(l.buf) < l.capN {
|
|
l.buf = append(l.buf, line)
|
|
return
|
|
}
|
|
l.full = true
|
|
l.buf[l.offset] = line
|
|
l.offset = (l.offset + 1) % l.capN
|
|
}
|
|
|
|
// Tail returns the most recent n lines in chronological order. If n
|
|
// is larger than the buffer capacity, only the held lines are
|
|
// returned. n <= 0 returns an empty slice.
|
|
func (l *LogBuffer) Tail(n int) []string {
|
|
if l == nil || n <= 0 {
|
|
return nil
|
|
}
|
|
l.mu.RLock()
|
|
defer l.mu.RUnlock()
|
|
size := len(l.buf)
|
|
if size == 0 {
|
|
return nil
|
|
}
|
|
if n > size {
|
|
n = size
|
|
}
|
|
out := make([]string, n)
|
|
if !l.full {
|
|
// Buffer not yet wrapped: just slice the tail.
|
|
copy(out, l.buf[size-n:])
|
|
return out
|
|
}
|
|
// Buffer is wrapped. The oldest line lives at l.offset; the
|
|
// newest line lives at (offset - 1 + capN) % capN.
|
|
idx := (l.offset - n + l.capN) % l.capN
|
|
for i := 0; i < n; i++ {
|
|
out[i] = l.buf[idx]
|
|
idx = (idx + 1) % l.capN
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Size reports the current number of stored lines.
|
|
func (l *LogBuffer) Size() int {
|
|
if l == nil {
|
|
return 0
|
|
}
|
|
l.mu.RLock()
|
|
defer l.mu.RUnlock()
|
|
return len(l.buf)
|
|
}
|
|
|
|
// Cap reports the maximum number of lines the buffer holds.
|
|
func (l *LogBuffer) Cap() int {
|
|
if l == nil {
|
|
return 0
|
|
}
|
|
return l.capN
|
|
}
|