blob: dd3bf4e8b4b9adc6e40a75f33e2d89785bfd965a [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.
14type LogReadOption struct {
Serge Bazanskif68153c2020-10-26 13:54:37 +010015 withChildren bool
16 withStream bool
17 withBacklog int
18 onlyLeveled bool
19 onlyRaw bool
Serge Bazanski3c5d0632024-09-12 10:49:12 +000020 leveledWithMinimumSeverity logging.Severity
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020021}
22
Serge Bazanski216fe7b2021-05-21 18:36:16 +020023// WithChildren makes Read return/stream data for both a given DN and all its
24// children.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020025func WithChildren() LogReadOption { return LogReadOption{withChildren: true} }
26
Serge Bazanski216fe7b2021-05-21 18:36:16 +020027// WithStream makes Read return a stream of data. This works alongside WithBacklog
28// to create a read-and-stream construct.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020029func WithStream() LogReadOption { return LogReadOption{withStream: true} }
30
Serge Bazanski216fe7b2021-05-21 18:36:16 +020031// WithBacklog makes Read return already recorded log entries, up to count
32// elements.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020033func WithBacklog(count int) LogReadOption { return LogReadOption{withBacklog: count} }
34
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035// BacklogAllAvailable makes WithBacklog return all backlogged log data that
36// logtree possesses.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020037const BacklogAllAvailable int = -1
38
Serge Bazanskif68153c2020-10-26 13:54:37 +010039func OnlyRaw() LogReadOption { return LogReadOption{onlyRaw: true} }
40
41func OnlyLeveled() LogReadOption { return LogReadOption{onlyLeveled: true} }
42
Serge Bazanski216fe7b2021-05-21 18:36:16 +020043// LeveledWithMinimumSeverity makes Read return only log entries that are at least
44// at a given Severity. If only leveled entries are needed, OnlyLeveled must be
45// used. This is a no-op when OnlyRaw is used.
Serge Bazanski3c5d0632024-09-12 10:49:12 +000046func LeveledWithMinimumSeverity(s logging.Severity) LogReadOption {
Serge Bazanskif68153c2020-10-26 13:54:37 +010047 return LogReadOption{leveledWithMinimumSeverity: s}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020048}
49
Serge Bazanski216fe7b2021-05-21 18:36:16 +020050// LogReader permits reading an already existing backlog of log entries and to
51// stream further ones.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020052type LogReader struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020053 // Backlog are the log entries already logged by LogTree. This will only be set if
54 // WithBacklog has been passed to Read.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020055 Backlog []*LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020056 // Stream is a channel of new entries as received live by LogTree. This will only
57 // be set if WithStream has been passed to Read. In this case, entries from this
58 // channel must be read as fast as possible by the consumer in order to prevent
59 // missing entries.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020060 Stream <-chan *LogEntry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020061 // done is channel used to signal (by closing) that the log consumer is not
62 // interested in more Stream data.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020063 done chan<- struct{}
Serge Bazanski216fe7b2021-05-21 18:36:16 +020064 // missed is an atomic integer pointer that tells the subscriber how many messages
65 // in Stream they missed. This pointer is nil if no streaming has been requested.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020066 missed *uint64
67}
68
Serge Bazanski216fe7b2021-05-21 18:36:16 +020069// Missed returns the amount of entries that were missed from Stream (as the
70// channel was not drained fast enough).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020071func (l *LogReader) Missed() uint64 {
72 // No Stream.
73 if l.missed == nil {
74 return 0
75 }
76 return atomic.LoadUint64(l.missed)
77}
78
Serge Bazanski216fe7b2021-05-21 18:36:16 +020079// Close closes the LogReader's Stream. This must be called once the Reader does
80// not wish to receive streaming messages anymore.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020081func (l *LogReader) Close() {
82 if l.done != nil {
83 close(l.done)
84 }
85}
86
Serge Bazanskib0272182020-11-02 18:39:44 +010087var (
88 ErrRawAndLeveled = errors.New("cannot return logs that are simultaneously OnlyRaw and OnlyLeveled")
89)
90
Serge Bazanski216fe7b2021-05-21 18:36:16 +020091// Read and/or stream entries from a LogTree. The returned LogReader is influenced
92// by the LogReadOptions passed, which influence whether the Read will return
93// existing entries, a stream, or both. In addition the options also dictate
94// whether only entries for that particular DN are returned, or for all sub-DNs as
95// well.
Serge Bazanskif68153c2020-10-26 13:54:37 +010096func (l *LogTree) Read(dn DN, opts ...LogReadOption) (*LogReader, error) {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020097 l.journal.mu.RLock()
98 defer l.journal.mu.RUnlock()
99
100 var backlog int
101 var stream bool
102 var recursive bool
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000103 var leveledSeverity logging.Severity
Serge Bazanskif68153c2020-10-26 13:54:37 +0100104 var onlyRaw, onlyLeveled bool
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200105
106 for _, opt := range opts {
107 if opt.withBacklog > 0 || opt.withBacklog == BacklogAllAvailable {
108 backlog = opt.withBacklog
109 }
110 if opt.withStream {
111 stream = true
112 }
113 if opt.withChildren {
114 recursive = true
115 }
Serge Bazanskif68153c2020-10-26 13:54:37 +0100116 if opt.leveledWithMinimumSeverity != "" {
117 leveledSeverity = opt.leveledWithMinimumSeverity
118 }
119 if opt.onlyLeveled {
120 onlyLeveled = true
121 }
122 if opt.onlyRaw {
123 onlyRaw = true
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200124 }
125 }
126
Serge Bazanskif68153c2020-10-26 13:54:37 +0100127 if onlyLeveled && onlyRaw {
Serge Bazanskib0272182020-11-02 18:39:44 +0100128 return nil, ErrRawAndLeveled
Serge Bazanskif68153c2020-10-26 13:54:37 +0100129 }
130
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200131 var filters []filter
Serge Bazanskif68153c2020-10-26 13:54:37 +0100132 if onlyLeveled {
133 filters = append(filters, filterOnlyLeveled)
134 }
135 if onlyRaw {
136 filters = append(filters, filterOnlyRaw)
137 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200138 if recursive {
139 filters = append(filters, filterSubtree(dn))
140 } else {
141 filters = append(filters, filterExact(dn))
142 }
Serge Bazanskif68153c2020-10-26 13:54:37 +0100143 if leveledSeverity != "" {
144 filters = append(filters, filterSeverity(leveledSeverity))
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200145 }
146
147 var entries []*entry
148 if backlog > 0 || backlog == BacklogAllAvailable {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200149 if recursive {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200150 entries = l.journal.scanEntries(backlog, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200151 } else {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200152 entries = l.journal.getEntries(backlog, dn, filters...)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200153 }
154 }
155
156 var sub *subscriber
157 if stream {
158 sub = &subscriber{
159 // TODO(q3k): make buffer size configurable
160 dataC: make(chan *LogEntry, 128),
161 doneC: make(chan struct{}),
162 filters: filters,
163 }
164 l.journal.subscribe(sub)
165 }
166
167 lr := &LogReader{}
168 lr.Backlog = make([]*LogEntry, len(entries))
169 for i, entry := range entries {
Serge Bazanskif68153c2020-10-26 13:54:37 +0100170 lr.Backlog[i] = entry.external()
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200171 }
172 if stream {
173 lr.Stream = sub.dataC
174 lr.done = sub.doneC
175 lr.missed = &sub.missed
176 }
Serge Bazanskif68153c2020-10-26 13:54:37 +0100177 return lr, nil
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200178}