blob: cd184201bb731b7c476d34b8dcfb931a5a2f2428 [file] [log] [blame] [edit]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package logbuffer implements a fixed-size in-memory ring buffer for
// line-separated logs. It implements io.Writer and splits the data into lines.
// The lines are kept in a ring where the oldest are overwritten once it's
// full. It allows retrieval of the last n lines. There is a built-in line
// length limit to bound the memory usage at maxLineLength * size.
package logbuffer
import (
"sync"
)
// LogBuffer implements a fixed-size in-memory ring buffer for line-separated logs
type LogBuffer struct {
mu sync.RWMutex
content []Line
length int
*LineBuffer
}
// New creates a new LogBuffer with a given ringbuffer size and maximum line
// length.
func New(size, maxLineLength int) *LogBuffer {
lb := &LogBuffer{
content: make([]Line, size),
}
lb.LineBuffer = NewLineBuffer(maxLineLength, lb.lineCallback)
return lb
}
func (b *LogBuffer) lineCallback(line *Line) {
b.mu.Lock()
defer b.mu.Unlock()
b.content[b.length%len(b.content)] = *line
b.length++
}
// capToContentLength caps the number of requested lines to what is actually
// available
func (b *LogBuffer) capToContentLength(n int) int {
// If there aren't enough lines to read, reduce the request size
if n > b.length {
n = b.length
}
// If there isn't enough ringbuffer space, reduce the request size
if n > len(b.content) {
n = len(b.content)
}
return n
}
// ReadLines reads the last n lines from the buffer in chronological order. If
// n is bigger than the ring buffer or the number of available lines only the
// number of stored lines are returned.
func (b *LogBuffer) ReadLines(n int) []Line {
b.mu.RLock()
defer b.mu.RUnlock()
n = b.capToContentLength(n)
// Copy references out to keep them around
outArray := make([]Line, n)
for i := 1; i <= n; i++ {
outArray[n-i] = b.content[(b.length-i)%len(b.content)]
}
return outArray
}
// ReadLinesTruncated works exactly the same as ReadLines, but adds an ellipsis
// at the end of every line that was truncated because it was over
// MaxLineLength
func (b *LogBuffer) ReadLinesTruncated(n int, ellipsis string) []string {
b.mu.RLock()
defer b.mu.RUnlock()
// This does not use ReadLines() to prevent excessive reference copying and
// associated GC pressure since it could process a lot of lines.
n = b.capToContentLength(n)
outArray := make([]string, n)
for i := 1; i <= n; i++ {
line := b.content[(b.length-i)%len(b.content)]
outArray[n-i] = line.String()
}
return outArray
}