| // 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 |
| } |