// Package logbuf provides an in-memory ring of recent log lines. The daemon // tees its slog output into one of these so `rtsp-streamer logs` can retrieve // recent activity over the control socket — the headless Pi has no reachable // place for the daemon's stderr to land (it goes to sway on tty1), so keeping // a buffer in the process is the practical way to inspect it over SSH. package logbuf import ( "strings" "sync" ) // Writer is an io.Writer that retains the last N newline-delimited lines // written to it. It is safe for concurrent use, so it can sit behind an // io.MultiWriter alongside os.Stderr in a slog handler. type Writer struct { mu sync.Mutex lines []string max int } // New returns a ring holding at most max lines. func New(max int) *Writer { if max < 1 { max = 1 } return &Writer{max: max, lines: make([]string, 0, max)} } // Write appends each complete line in p to the ring, dropping the oldest when // it overflows. It always reports the full length written so it never trips up // the MultiWriter it sits behind. func (w *Writer) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() for _, line := range strings.Split(strings.TrimRight(string(p), "\n"), "\n") { if line == "" { continue } w.lines = append(w.lines, line) } if len(w.lines) > w.max { w.lines = append(w.lines[:0], w.lines[len(w.lines)-w.max:]...) } return len(p), nil } // Lines returns a copy of the buffered lines, oldest first. When n > 0 only // the most recent n are returned. func (w *Writer) Lines(n int) []string { w.mu.Lock() defer w.mu.Unlock() start := 0 if n > 0 && n < len(w.lines) { start = len(w.lines) - n } out := make([]string, len(w.lines)-start) copy(out, w.lines[start:]) return out }