blob: fe58fbecec8540541f18843a44e2b94ee5bcae59 [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"
Serge Bazanskif68153c2020-10-26 13:54:37 +01008 "sync/atomic"
Serge Bazanski3c5d0632024-09-12 10:49:12 +00009
10 "source.monogon.dev/go/logging"
Serge Bazanskif68153c2020-10-26 13:54:37 +010011)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020012
13// LogReadOption describes options for the LogTree.Read call.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020014type LogReadOption func(*logReaderOptions)
15
16type logReaderOptions struct {
Serge Bazanskif68153c2020-10-26 13:54:37 +010017 withChildren bool
18 withStream bool
19 withBacklog int
20 onlyLeveled bool
21 onlyRaw bool
Serge Bazanski3c5d0632024-09-12 10:49:12 +000022 leveledWithMinimumSeverity logging.Severity
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020023}
24
Serge Bazanski216fe7b2021-05-21 18:36:16 +020025// WithChildren makes Read return/stream data for both a given DN and all its
26// children.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020027func WithChildren() LogReadOption {
28 return func(lro *logReaderOptions) {
29 lro.withChildren = true
30 }
31}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020032
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033// WithStream makes Read return a stream of data. This works alongside WithBacklog
34// to create a read-and-stream construct.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020035func WithStream() LogReadOption {
36 return func(lro *logReaderOptions) {
37 lro.withStream = true
38 }
39}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020040
Serge Bazanski216fe7b2021-05-21 18:36:16 +020041// WithBacklog makes Read return already recorded log entries, up to count
42// elements.
Tim Windelschmidt50093be2025-07-21 17:39:09 +020043func WithBacklog(count int) LogReadOption {
44 return func(lro *logReaderOptions) { lro.withBacklog = count }
45}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020046
Serge Bazanski216fe7b2021-05-21 18:36:16 +020047// BacklogAllAvailable makes WithBacklog return all backlogged log data that
48// logtree possesses.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020049const BacklogAllAvailable int = -1
50
Tim Windelschmidt50093be2025-07-21 17:39:09 +020051func OnlyRaw() LogReadOption { return func(lro *logReaderOptions) { lro.onlyRaw = true } }
Serge Bazanskif68153c2020-10-26 13:54:37 +010052
Tim Windelschmidt50093be2025-07-21 17:39:09 +020053func OnlyLeveled() LogReadOption { return func(lro *logReaderOptions) { lro.onlyLeveled = true } }
Serge Bazanskif68153c2020-10-26 13:54:37 +010054
Serge Bazanski216fe7b2021-05-21 18:36:16 +020055// LeveledWithMinimumSeverity makes Read return only log entries that are at least
56// at a given Severity. If only leveled entries are needed, OnlyLeveled must be
57// used. This is a no-op when OnlyRaw is used.
Serge Bazanski3c5d0632024-09-12 10:49:12 +000058func LeveledWithMinimumSeverity(s logging.Severity) LogReadOption {
Tim Windelschmidt50093be2025-07-21 17:39:09 +020059 return func(lro *logReaderOptions) { lro.leveledWithMinimumSeverity = s }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020060}
61
Serge Bazanski216fe7b2021-05-21 18:36:16 +020062// LogReader permits reading an already existing backlog of log entries and to
63// stream further ones.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020064type LogReader struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020065 // Backlog are the log entries already logged by LogTree. This will only be set if
66 // WithBacklog has been passed to Read.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020067 Backlog []*LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020068 // Stream is a channel of new entries as received live by LogTree. This will only
69 // be set if WithStream has been passed to Read. In this case, entries from this
70 // channel must be read as fast as possible by the consumer in order to prevent
71 // missing entries.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020072 Stream <-chan *LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020073 // done is channel used to signal (by closing) that the log consumer is not
74 // interested in more Stream data.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020075 done chan<- struct{}
Serge Bazanski216fe7b2021-05-21 18:36:16 +020076 // missed is an atomic integer pointer that tells the subscriber how many messages
77 // in Stream they missed. This pointer is nil if no streaming has been requested.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020078 missed *uint64
79}
80
Serge Bazanski216fe7b2021-05-21 18:36:16 +020081// Missed returns the amount of entries that were missed from Stream (as the
82// channel was not drained fast enough).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020083func (l *LogReader) Missed() uint64 {
84 // No Stream.
85 if l.missed == nil {
86 return 0
87 }
88 return atomic.LoadUint64(l.missed)
89}
90
Serge Bazanski216fe7b2021-05-21 18:36:16 +020091// Close closes the LogReader's Stream. This must be called once the Reader does
92// not wish to receive streaming messages anymore.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020093func (l *LogReader) Close() {
94 if l.done != nil {
95 close(l.done)
96 }
97}
98
Serge Bazanskib0272182020-11-02 18:39:44 +010099var (
100 ErrRawAndLeveled = errors.New("cannot return logs that are simultaneously OnlyRaw and OnlyLeveled")
101)
102
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200103// Read and/or stream entries from a LogTree. The returned LogReader is influenced
104// by the LogReadOptions passed, which influence whether the Read will return
105// existing entries, a stream, or both. In addition the options also dictate
106// whether only entries for that particular DN are returned, or for all sub-DNs as
107// well.
Serge Bazanskif68153c2020-10-26 13:54:37 +0100108func (l *LogTree) Read(dn DN, opts ...LogReadOption) (*LogReader, error) {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200109 l.journal.mu.RLock()
110 defer l.journal.mu.RUnlock()
111
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200112 var lro logReaderOptions
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200113
114 for _, opt := range opts {
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200115 opt(&lro)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200116 }
117
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200118 if lro.onlyLeveled && lro.onlyRaw {
Serge Bazanskib0272182020-11-02 18:39:44 +0100119 return nil, ErrRawAndLeveled
Serge Bazanskif68153c2020-10-26 13:54:37 +0100120 }
121
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200122 var filters []filter
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200123 if lro.onlyLeveled {
Serge Bazanskif68153c2020-10-26 13:54:37 +0100124 filters = append(filters, filterOnlyLeveled)
125 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200126 if lro.onlyRaw {
Serge Bazanskif68153c2020-10-26 13:54:37 +0100127 filters = append(filters, filterOnlyRaw)
128 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200129 if lro.withChildren {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200130 filters = append(filters, filterSubtree(dn))
131 } else {
132 filters = append(filters, filterExact(dn))
133 }
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200134 if lro.leveledWithMinimumSeverity != "" {
135 filters = append(filters, filterSeverity(lro.leveledWithMinimumSeverity))
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200136 }
137
138 var entries []*entry
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200139 if lro.withBacklog > 0 || lro.withBacklog == BacklogAllAvailable {
140 if lro.withChildren {
141 entries = l.journal.scanEntries(lro.withBacklog, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200142 } else {
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200143 entries = l.journal.getEntries(lro.withBacklog, dn, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200144 }
145 }
146
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200147 lr := &LogReader{}
Tim Windelschmidt50093be2025-07-21 17:39:09 +0200148 if lro.withStream {
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200149 sub := &subscriber{
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200150 // TODO(q3k): make buffer size configurable
151 dataC: make(chan *LogEntry, 128),
152 doneC: make(chan struct{}),
153 filters: filters,
154 }
155 l.journal.subscribe(sub)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200156
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200157 lr.Stream = sub.dataC
158 lr.done = sub.doneC
159 lr.missed = &sub.missed
160 }
Tim Windelschmidtce0ea8f2025-07-21 19:13:43 +0200161
162 lr.Backlog = make([]*LogEntry, len(entries))
163 for i, entry := range entries {
164 lr.Backlog[i] = entry.external()
165 }
166
Serge Bazanskif68153c2020-10-26 13:54:37 +0100167 return lr, nil
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200168}