blob: de115f8f58450b562caef0a3d2f2999a5f7b5bad [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun25b82a82020-03-23 20:27:51 +01002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun25b82a82020-03-23 20:27:51 +01003
Serge Bazanski216fe7b2021-05-21 18:36:16 +02004// Package logbuffer implements a fixed-size in-memory ring buffer for
5// line-separated logs. It implements io.Writer and splits the data into lines.
6// The lines are kept in a ring where the oldest are overwritten once it's
7// full. It allows retrieval of the last n lines. There is a built-in line
8// length limit to bound the memory usage at maxLineLength * size.
Lorenz Brun25b82a82020-03-23 20:27:51 +01009package logbuffer
10
11import (
Lorenz Brun25b82a82020-03-23 20:27:51 +010012 "sync"
13)
14
15// LogBuffer implements a fixed-size in-memory ring buffer for line-separated logs
16type LogBuffer struct {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010017 mu sync.RWMutex
18 content []Line
19 length int
20 *LineBuffer
Lorenz Brun25b82a82020-03-23 20:27:51 +010021}
22
Serge Bazanski216fe7b2021-05-21 18:36:16 +020023// New creates a new LogBuffer with a given ringbuffer size and maximum line
24// length.
Lorenz Brun25b82a82020-03-23 20:27:51 +010025func New(size, maxLineLength int) *LogBuffer {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010026 lb := &LogBuffer{
27 content: make([]Line, size),
Lorenz Brun25b82a82020-03-23 20:27:51 +010028 }
Serge Bazanski248b2ec2020-10-26 15:55:51 +010029 lb.LineBuffer = NewLineBuffer(maxLineLength, lb.lineCallback)
30 return lb
Lorenz Brun25b82a82020-03-23 20:27:51 +010031}
32
Serge Bazanski248b2ec2020-10-26 15:55:51 +010033func (b *LogBuffer) lineCallback(line *Line) {
Lorenz Brun25b82a82020-03-23 20:27:51 +010034 b.mu.Lock()
35 defer b.mu.Unlock()
36
Serge Bazanski248b2ec2020-10-26 15:55:51 +010037 b.content[b.length%len(b.content)] = *line
38 b.length++
Lorenz Brun25b82a82020-03-23 20:27:51 +010039}
40
Serge Bazanski216fe7b2021-05-21 18:36:16 +020041// capToContentLength caps the number of requested lines to what is actually
42// available
Lorenz Brun25b82a82020-03-23 20:27:51 +010043func (b *LogBuffer) capToContentLength(n int) int {
44 // If there aren't enough lines to read, reduce the request size
45 if n > b.length {
46 n = b.length
47 }
48 // If there isn't enough ringbuffer space, reduce the request size
49 if n > len(b.content) {
50 n = len(b.content)
51 }
52 return n
53}
54
Serge Bazanski216fe7b2021-05-21 18:36:16 +020055// ReadLines reads the last n lines from the buffer in chronological order. If
56// n is bigger than the ring buffer or the number of available lines only the
57// number of stored lines are returned.
Lorenz Brun25b82a82020-03-23 20:27:51 +010058func (b *LogBuffer) ReadLines(n int) []Line {
59 b.mu.RLock()
60 defer b.mu.RUnlock()
61
62 n = b.capToContentLength(n)
63
64 // Copy references out to keep them around
65 outArray := make([]Line, n)
66 for i := 1; i <= n; i++ {
67 outArray[n-i] = b.content[(b.length-i)%len(b.content)]
68 }
69 return outArray
70}
71
Serge Bazanski216fe7b2021-05-21 18:36:16 +020072// ReadLinesTruncated works exactly the same as ReadLines, but adds an ellipsis
73// at the end of every line that was truncated because it was over
74// MaxLineLength
Lorenz Brun25b82a82020-03-23 20:27:51 +010075func (b *LogBuffer) ReadLinesTruncated(n int, ellipsis string) []string {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010076 b.mu.RLock()
77 defer b.mu.RUnlock()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020078 // This does not use ReadLines() to prevent excessive reference copying and
79 // associated GC pressure since it could process a lot of lines.
Lorenz Brun25b82a82020-03-23 20:27:51 +010080
81 n = b.capToContentLength(n)
82
83 outArray := make([]string, n)
84 for i := 1; i <= n; i++ {
85 line := b.content[(b.length-i)%len(b.content)]
Serge Bazanski248b2ec2020-10-26 15:55:51 +010086 outArray[n-i] = line.String()
Lorenz Brun25b82a82020-03-23 20:27:51 +010087 }
88 return outArray
89}