87 lines
2 KiB
Go
87 lines
2 KiB
Go
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package eventlog
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
const maxRecordBytes = 1 << 20
|
|
|
|
// ReadRecent returns at most limit records in chronological order, reading the
|
|
// older rotated segment before the active file. Every returned row is valid
|
|
// JSON; corruption is reported instead of passed to an operator as evidence.
|
|
func ReadRecent(path string, limit int) ([][]byte, error) {
|
|
if path == "" {
|
|
return nil, fmt.Errorf("event log path is required")
|
|
}
|
|
if limit <= 0 {
|
|
return nil, fmt.Errorf("event limit must be positive")
|
|
}
|
|
buffer := recentBuffer{limit: limit}
|
|
if _, err := os.Stat(path + ".1"); err == nil {
|
|
if err := scanRecords(path+".1", buffer.add); err != nil {
|
|
return nil, err
|
|
}
|
|
} else if !os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("stat rotated event log: %w", err)
|
|
}
|
|
if err := scanRecords(path, buffer.add); err != nil {
|
|
return nil, err
|
|
}
|
|
return buffer.values(), nil
|
|
}
|
|
|
|
func scanRecords(path string, consume func([]byte)) error {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return fmt.Errorf("open event log %s: %w", path, err)
|
|
}
|
|
defer file.Close()
|
|
scanner := bufio.NewScanner(file)
|
|
scanner.Buffer(make([]byte, 64*1024), maxRecordBytes)
|
|
line := 0
|
|
for scanner.Scan() {
|
|
line++
|
|
raw := scanner.Bytes()
|
|
if len(raw) == 0 {
|
|
continue
|
|
}
|
|
if !json.Valid(raw) {
|
|
return fmt.Errorf("invalid JSON in %s at line %d", path, line)
|
|
}
|
|
consume(append([]byte(nil), raw...))
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return fmt.Errorf("read event log %s: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type recentBuffer struct {
|
|
limit int
|
|
rows [][]byte
|
|
next int
|
|
}
|
|
|
|
func (b *recentBuffer) add(raw []byte) {
|
|
if len(b.rows) < b.limit {
|
|
b.rows = append(b.rows, raw)
|
|
return
|
|
}
|
|
b.rows[b.next] = raw
|
|
b.next = (b.next + 1) % b.limit
|
|
}
|
|
|
|
func (b *recentBuffer) values() [][]byte {
|
|
if len(b.rows) < b.limit || b.next == 0 {
|
|
return b.rows
|
|
}
|
|
result := make([][]byte, 0, len(b.rows))
|
|
result = append(result, b.rows[b.next:]...)
|
|
result = append(result, b.rows[:b.next]...)
|
|
return result
|
|
}
|