blob: cd184201bb731b7c476d34b8dcfb931a5a2f2428 [file] [log] [blame]
Lorenz Brun25b82a82020-03-23 20:27:51 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski216fe7b2021-05-21 18:36:16 +020017// Package logbuffer implements a fixed-size in-memory ring buffer for
18// line-separated logs. It implements io.Writer and splits the data into lines.
19// The lines are kept in a ring where the oldest are overwritten once it's
20// full. It allows retrieval of the last n lines. There is a built-in line
21// length limit to bound the memory usage at maxLineLength * size.
Lorenz Brun25b82a82020-03-23 20:27:51 +010022package logbuffer
23
24import (
Lorenz Brun25b82a82020-03-23 20:27:51 +010025 "sync"
26)
27
28// LogBuffer implements a fixed-size in-memory ring buffer for line-separated logs
29type LogBuffer struct {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010030 mu sync.RWMutex
31 content []Line
32 length int
33 *LineBuffer
Lorenz Brun25b82a82020-03-23 20:27:51 +010034}
35
Serge Bazanski216fe7b2021-05-21 18:36:16 +020036// New creates a new LogBuffer with a given ringbuffer size and maximum line
37// length.
Lorenz Brun25b82a82020-03-23 20:27:51 +010038func New(size, maxLineLength int) *LogBuffer {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010039 lb := &LogBuffer{
40 content: make([]Line, size),
Lorenz Brun25b82a82020-03-23 20:27:51 +010041 }
Serge Bazanski248b2ec2020-10-26 15:55:51 +010042 lb.LineBuffer = NewLineBuffer(maxLineLength, lb.lineCallback)
43 return lb
Lorenz Brun25b82a82020-03-23 20:27:51 +010044}
45
Serge Bazanski248b2ec2020-10-26 15:55:51 +010046func (b *LogBuffer) lineCallback(line *Line) {
Lorenz Brun25b82a82020-03-23 20:27:51 +010047 b.mu.Lock()
48 defer b.mu.Unlock()
49
Serge Bazanski248b2ec2020-10-26 15:55:51 +010050 b.content[b.length%len(b.content)] = *line
51 b.length++
Lorenz Brun25b82a82020-03-23 20:27:51 +010052}
53
Serge Bazanski216fe7b2021-05-21 18:36:16 +020054// capToContentLength caps the number of requested lines to what is actually
55// available
Lorenz Brun25b82a82020-03-23 20:27:51 +010056func (b *LogBuffer) capToContentLength(n int) int {
57 // If there aren't enough lines to read, reduce the request size
58 if n > b.length {
59 n = b.length
60 }
61 // If there isn't enough ringbuffer space, reduce the request size
62 if n > len(b.content) {
63 n = len(b.content)
64 }
65 return n
66}
67
Serge Bazanski216fe7b2021-05-21 18:36:16 +020068// ReadLines reads the last n lines from the buffer in chronological order. If
69// n is bigger than the ring buffer or the number of available lines only the
70// number of stored lines are returned.
Lorenz Brun25b82a82020-03-23 20:27:51 +010071func (b *LogBuffer) ReadLines(n int) []Line {
72 b.mu.RLock()
73 defer b.mu.RUnlock()
74
75 n = b.capToContentLength(n)
76
77 // Copy references out to keep them around
78 outArray := make([]Line, n)
79 for i := 1; i <= n; i++ {
80 outArray[n-i] = b.content[(b.length-i)%len(b.content)]
81 }
82 return outArray
83}
84
Serge Bazanski216fe7b2021-05-21 18:36:16 +020085// ReadLinesTruncated works exactly the same as ReadLines, but adds an ellipsis
86// at the end of every line that was truncated because it was over
87// MaxLineLength
Lorenz Brun25b82a82020-03-23 20:27:51 +010088func (b *LogBuffer) ReadLinesTruncated(n int, ellipsis string) []string {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010089 b.mu.RLock()
90 defer b.mu.RUnlock()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020091 // This does not use ReadLines() to prevent excessive reference copying and
92 // associated GC pressure since it could process a lot of lines.
Lorenz Brun25b82a82020-03-23 20:27:51 +010093
94 n = b.capToContentLength(n)
95
96 outArray := make([]string, n)
97 for i := 1; i <= n; i++ {
98 line := b.content[(b.length-i)%len(b.content)]
Serge Bazanski248b2ec2020-10-26 15:55:51 +010099 outArray[n-i] = line.String()
Lorenz Brun25b82a82020-03-23 20:27:51 +0100100 }
101 return outArray
102}