blob: ce478164da6ff5b485768ae2817dad83347e400b [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
17// Package logbuffer implements a fixed-size in-memory ring buffer for line-separated logs.
18// It implements io.Writer and splits the data into lines. The lines are kept in a ring where the
19// oldest are overwritten once it's full. It allows retrieval of the last n lines. There is a built-in
20// line length limit to bound the memory usage at maxLineLength * size.
21package logbuffer
22
23import (
Lorenz Brun25b82a82020-03-23 20:27:51 +010024 "sync"
25)
26
27// LogBuffer implements a fixed-size in-memory ring buffer for line-separated logs
28type LogBuffer struct {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010029 mu sync.RWMutex
30 content []Line
31 length int
32 *LineBuffer
Lorenz Brun25b82a82020-03-23 20:27:51 +010033}
34
Serge Bazanski248b2ec2020-10-26 15:55:51 +010035// New creates a new LogBuffer with a given ringbuffer size and maximum line length.
Lorenz Brun25b82a82020-03-23 20:27:51 +010036func New(size, maxLineLength int) *LogBuffer {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010037 lb := &LogBuffer{
38 content: make([]Line, size),
Lorenz Brun25b82a82020-03-23 20:27:51 +010039 }
Serge Bazanski248b2ec2020-10-26 15:55:51 +010040 lb.LineBuffer = NewLineBuffer(maxLineLength, lb.lineCallback)
41 return lb
Lorenz Brun25b82a82020-03-23 20:27:51 +010042}
43
Serge Bazanski248b2ec2020-10-26 15:55:51 +010044func (b *LogBuffer) lineCallback(line *Line) {
Lorenz Brun25b82a82020-03-23 20:27:51 +010045 b.mu.Lock()
46 defer b.mu.Unlock()
47
Serge Bazanski248b2ec2020-10-26 15:55:51 +010048 b.content[b.length%len(b.content)] = *line
49 b.length++
Lorenz Brun25b82a82020-03-23 20:27:51 +010050}
51
52// capToContentLength caps the number of requested lines to what is actually available
53func (b *LogBuffer) capToContentLength(n int) int {
54 // If there aren't enough lines to read, reduce the request size
55 if n > b.length {
56 n = b.length
57 }
58 // If there isn't enough ringbuffer space, reduce the request size
59 if n > len(b.content) {
60 n = len(b.content)
61 }
62 return n
63}
64
65// ReadLines reads the last n lines from the buffer in chronological order. If n is bigger than the
66// ring buffer or the number of available lines only the number of stored lines are returned.
67func (b *LogBuffer) ReadLines(n int) []Line {
68 b.mu.RLock()
69 defer b.mu.RUnlock()
70
71 n = b.capToContentLength(n)
72
73 // Copy references out to keep them around
74 outArray := make([]Line, n)
75 for i := 1; i <= n; i++ {
76 outArray[n-i] = b.content[(b.length-i)%len(b.content)]
77 }
78 return outArray
79}
80
81// ReadLinesTruncated works exactly the same as ReadLines, but adds an ellipsis at the end of every
82// line that was truncated because it was over MaxLineLength
83func (b *LogBuffer) ReadLinesTruncated(n int, ellipsis string) []string {
Serge Bazanski248b2ec2020-10-26 15:55:51 +010084 b.mu.RLock()
85 defer b.mu.RUnlock()
Lorenz Brun25b82a82020-03-23 20:27:51 +010086 // This does not use ReadLines() to prevent excessive reference copying and associated GC pressure
87 // since it could process a lot of lines.
88
89 n = b.capToContentLength(n)
90
91 outArray := make([]string, n)
92 for i := 1; i <= n; i++ {
93 line := b.content[(b.length-i)%len(b.content)]
Serge Bazanski248b2ec2020-10-26 15:55:51 +010094 outArray[n-i] = line.String()
Lorenz Brun25b82a82020-03-23 20:27:51 +010095 }
96 return outArray
97}