Files
worker/internal/webapp/logbuffer.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

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
}