blob: 6c424521a0a02f1e6190e30173d3f60072ab7a1c [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02003
4package logtree
5
Serge Bazanskif68153c2020-10-26 13:54:37 +01006import (
Serge Bazanskib0272182020-11-02 18:39:44 +01007 "errors"
Tim Windelschmidt3553b242025-07-21 18:19:05 +02008 "slices"
Serge Bazanskif68153c2020-10-26 13:54:37 +01009 "sync/atomic"
Serge Bazanski3c5d0632024-09-12 10:49:12 +000010
11 "source.monogon.dev/go/logging"
Serge Bazanskif68153c2020-10-26 13:54:37 +010012)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020013
Tim Windelschmidt3553b242025-07-21 18:19:05 +020014type BacklogOrder int
15
16const (
17 BacklogOrderLatestFirst BacklogOrder = iota
18 BacklogOrderOldestFirst
19)
20
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020021// LogReadOption describes options for the LogTree.Read call.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020022type LogReadOption func(*logReaderOptions)
23
24type logReaderOptions struct {
Serge Bazanskif68153c2020-10-26 13:54:37 +010025 withChildren bool
26 withStream bool
27 withBacklog int
Tim Windelschmidt3553b242025-07-21 18:19:05 +020028 withBacklogOrder BacklogOrder
Serge Bazanskif68153c2020-10-26 13:54:37 +010029 onlyLeveled bool
30 onlyRaw bool
Serge Bazanski3c5d0632024-09-12 10:49:12 +000031 leveledWithMinimumSeverity logging.Severity
Tim Windelschmidtc2635922025-07-21 18:01:23 +020032 withStreamBufferSize int
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020033}
34
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035// WithChildren makes Read return/stream data for both a given DN and all its
36// children.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020037func WithChildren() LogReadOption {
38 return func(lro *logReaderOptions) {
39 lro.withChildren = true
40 }
41}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020042
Serge Bazanski216fe7b2021-05-21 18:36:16 +020043// WithStream makes Read return a stream of data. This works alongside WithBacklog
44// to create a read-and-stream construct.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020045func WithStream() LogReadOption {
46 return func(lro *logReaderOptions) {
47 lro.withStream = true
48 }
49}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020050
Tim Windelschmidtc2635922025-07-21 18:01:23 +020051// WithStreamBuffer applies WithStream and overrides the default stream buffer
52// size of 128.
53func WithStreamBuffer(size int) LogReadOption {
54 return func(lro *logReaderOptions) {
55 lro.withStreamBufferSize = size
56 lro.withStream = true
57 }
58}
59
Serge Bazanski216fe7b2021-05-21 18:36:16 +020060// WithBacklog makes Read return already recorded log entries, up to count
61// elements.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020062func WithBacklog(count int) LogReadOption {
63 return func(lro *logReaderOptions) { lro.withBacklog = count }
64}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020065
Tim Windelschmidt3553b242025-07-21 18:19:05 +020066// WithBacklogOrder makes Read return log entries in the given order,
67// default being latest messages first.
68func WithBacklogOrder(order BacklogOrder) LogReadOption {
69 return func(lro *logReaderOptions) { lro.withBacklogOrder = order }
70}
71
Serge Bazanski216fe7b2021-05-21 18:36:16 +020072// BacklogAllAvailable makes WithBacklog return all backlogged log data that
73// logtree possesses.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020074const BacklogAllAvailable int = -1
75
Tim Windelschmidt50093be2025-07-21 17:39:09 +020076func OnlyRaw() LogReadOption { return func(lro *logReaderOptions) { lro.onlyRaw = true } }
Serge Bazanskif68153c2020-10-26 13:54:37 +010077
Tim Windelschmidt50093be2025-07-21 17:39:09 +020078func OnlyLeveled() LogReadOption { return func(lro *logReaderOptions) { lro.onlyLeveled = true } }
Serge Bazanskif68153c2020-10-26 13:54:37 +010079
Serge Bazanski216fe7b2021-05-21 18:36:16 +020080// LeveledWithMinimumSeverity makes Read return only log entries that are at least
81// at a given Severity. If only leveled entries are needed, OnlyLeveled must be
82// used. This is a no-op when OnlyRaw is used.
Serge Bazanski3c5d0632024-09-12 10:49:12 +000083func LeveledWithMinimumSeverity(s logging.Severity) LogReadOption {
Tim Windelschmidt50093be2025-07-21 17:39:09 +020084 return func(lro *logReaderOptions) { lro.leveledWithMinimumSeverity = s }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020085}
86
Serge Bazanski216fe7b2021-05-21 18:36:16 +020087// LogReader permits reading an already existing backlog of log entries and to
88// stream further ones.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020089type LogReader struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020090 // Backlog are the log entries already logged by LogTree. This will only be set if
91 // WithBacklog has been passed to Read.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020092 Backlog []*LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020093 // Stream is a channel of new entries as received live by LogTree. This will only
94 // be set if WithStream has been passed to Read. In this case, entries from this
95 // channel must be read as fast as possible by the consumer in order to prevent
96 // missing entries.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020097 Stream <-chan *LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020098 // done is channel used to signal (by closing) that the log consumer is not
99 // interested in more Stream data.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200100 done chan<- struct{}
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200101 // missed is an atomic integer pointer that tells the subscriber how many messages
102 // in Stream they missed. This pointer is nil if no streaming has been requested.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200103 missed *uint64
104}
105
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200106// Missed returns the amount of entries that were missed from Stream (as the
107// channel was not drained fast enough).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200108func (l *LogReader) Missed() uint64 {
109 // No Stream.
110 if l.missed == nil {
111 return 0
112 }
113 return atomic.LoadUint64(l.missed)
114}
115
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200116// Close closes the LogReader's Stream. This must be called once the Reader does
117// not wish to receive streaming messages anymore.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200118func (l *LogReader) Close() {
119 if l.done != nil {
120 close(l.done)
121 }
122}
123
Serge Bazanskib0272182020-11-02 18:39:44 +0100124var (
125 ErrRawAndLeveled = errors.New("cannot return logs that are simultaneously OnlyRaw and OnlyLeveled")
126)
127
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200128// Read and/or stream entries from a LogTree. The returned LogReader is influenced
129// by the LogReadOptions passed, which influence whether the Read will return
130// existing entries, a stream, or both. In addition the options also dictate
131// whether only entries for that particular DN are returned, or for all sub-DNs as
132// well.
Serge Bazanskif68153c2020-10-26 13:54:37 +0100133func (l *LogTree) Read(dn DN, opts ...LogReadOption) (*LogReader, error) {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200134 l.journal.mu.RLock()
135 defer l.journal.mu.RUnlock()
136
Tim Windelschmidtc2635922025-07-21 18:01:23 +0200137 lro := logReaderOptions{
138 withStreamBufferSize: 128,
139 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200140
141 for _, opt := range opts {
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200142 opt(&lro)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200143 }
144
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200145 if lro.onlyLeveled && lro.onlyRaw {
Serge Bazanskib0272182020-11-02 18:39:44 +0100146 return nil, ErrRawAndLeveled
Serge Bazanskif68153c2020-10-26 13:54:37 +0100147 }
148
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200149 var filters []filter
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200150 if lro.onlyLeveled {
Serge Bazanskif68153c2020-10-26 13:54:37 +0100151 filters = append(filters, filterOnlyLeveled)
152 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200153 if lro.onlyRaw {
Serge Bazanskif68153c2020-10-26 13:54:37 +0100154 filters = append(filters, filterOnlyRaw)
155 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200156 if lro.withChildren {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200157 filters = append(filters, filterSubtree(dn))
158 } else {
159 filters = append(filters, filterExact(dn))
160 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200161 if lro.leveledWithMinimumSeverity != "" {
162 filters = append(filters, filterSeverity(lro.leveledWithMinimumSeverity))
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200163 }
164
165 var entries []*entry
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200166 if lro.withBacklog > 0 || lro.withBacklog == BacklogAllAvailable {
167 if lro.withChildren {
168 entries = l.journal.scanEntries(lro.withBacklog, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200169 } else {
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200170 entries = l.journal.getEntries(lro.withBacklog, dn, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200171 }
172 }
173
Tim Windelschmidt3553b242025-07-21 18:19:05 +0200174 if lro.withBacklogOrder == BacklogOrderLatestFirst {
175 // Reverse entries back into chronological order.
176 slices.Reverse(entries)
177 }
178
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200179 lr := &LogReader{}
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200180 if lro.withStream {
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200181 sub := &subscriber{
Tim Windelschmidtc2635922025-07-21 18:01:23 +0200182 dataC: make(chan *LogEntry, lro.withStreamBufferSize),
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200183 doneC: make(chan struct{}),
184 filters: filters,
185 }
186 l.journal.subscribe(sub)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200187
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200188 lr.Stream = sub.dataC
189 lr.done = sub.doneC
190 lr.missed = &sub.missed
191 }
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200192
193 lr.Backlog = make([]*LogEntry, len(entries))
194 for i, entry := range entries {
195 lr.Backlog[i] = entry.external()
196 }
197
Serge Bazanskif68153c2020-10-26 13:54:37 +0100198 return lr, nil
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200199}