blob: d492d160d1a87bcff150a27c45a7120974a704cc [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
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02006import "source.monogon.dev/osbase/logbuffer"
Serge Bazanskif68153c2020-10-26 13:54:37 +01007
Serge Bazanski216fe7b2021-05-21 18:36:16 +02008// entry is a journal entry, representing a single log event (encompassed in a
9// Payload) at a given DN. See the journal struct for more information about the
10// global/local linked lists.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020011type entry struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020012 // origin is the DN at which the log entry was recorded, or conversely, in which DN
13 // it will be available at.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020014 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020015 // journal is the parent journal of this entry. An entry can belong only to a
16 // single journal. This pointer is used to mutate the journal's head/tail pointers
17 // when unlinking an entry.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020018 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020019 // leveled is the leveled log entry for this entry, if this log entry was emitted
20 // by leveled logging. Otherwise it is nil.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020021 leveled *LeveledPayload
Serge Bazanski216fe7b2021-05-21 18:36:16 +020022 // raw is the raw log entry for this entry, if this log entry was emitted by raw
23 // logging. Otherwise it is nil.
Serge Bazanskif68153c2020-10-26 13:54:37 +010024 raw *logbuffer.Line
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020025
Serge Bazanski216fe7b2021-05-21 18:36:16 +020026 // prevGlobal is the previous entry in the global linked list, or nil if this entry
27 // is the oldest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020028 prevGlobal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020029 // nextGlobal is the next entry in the global linked list, or nil if this entry is
30 // the newest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020031 nextGlobal *entry
32
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033 // prevLocal is the previous entry in this entry DN's local linked list, or nil if
34 // this entry is the oldest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020035 prevLocal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020036 // prevLocal is the next entry in this entry DN's local linked list, or nil if this
37 // entry is the newest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020038 nextLocal *entry
39
Serge Bazanski216fe7b2021-05-21 18:36:16 +020040 // seqLocal is a counter within a local linked list that increases by one each time
41 // a new log entry is added. It is used to quickly establish local linked list
42 // sizes (by subtracting seqLocal from both ends). This setup allows for O(1)
43 // length calculation for local linked lists as long as entries are only unlinked
44 // from the head or tail (which is the case in the current implementation).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020045 seqLocal uint64
Tim Windelschmidt01491a72025-07-23 21:49:11 +020046
47 // seqGlobal is a counter within the global log structure that increases by
48 // one each time a new log entry is added.
49 seqGlobal uint64
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020050}
51
Tim Windelschmidt9e964882025-07-22 01:06:21 +020052// defaultDNQuota defines how many messages should be stored per DN.
53const defaultDNQuota = 8192
54
Serge Bazanski216fe7b2021-05-21 18:36:16 +020055// external returns a LogEntry object for this entry, ie. the public version of
56// this object, without fields relating to the parent journal, linked lists,
57// sequences, etc. These objects are visible to library consumers.
Serge Bazanskif68153c2020-10-26 13:54:37 +010058func (e *entry) external() *LogEntry {
59 return &LogEntry{
Tim Windelschmidt01491a72025-07-23 21:49:11 +020060 DN: e.origin,
61 Leveled: e.leveled,
62 Raw: e.raw,
63 Position: int(e.seqGlobal),
Serge Bazanskif68153c2020-10-26 13:54:37 +010064 }
65}
66
Serge Bazanski216fe7b2021-05-21 18:36:16 +020067// unlink removes this entry from both global and local linked lists, updating the
68// journal's head/tail pointers if needed. journal.mu must be taken as RW
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020069func (e *entry) unlink() {
70 // Unlink from the global linked list.
71 if e.prevGlobal != nil {
72 e.prevGlobal.nextGlobal = e.nextGlobal
73 }
74 if e.nextGlobal != nil {
75 e.nextGlobal.prevGlobal = e.prevGlobal
76 }
77 // Update journal head/tail pointers.
78 if e.journal.head == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020079 e.journal.head = e.nextGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020080 }
81 if e.journal.tail == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020082 e.journal.tail = e.prevGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020083 }
84
85 // Unlink from the local linked list.
86 if e.prevLocal != nil {
87 e.prevLocal.nextLocal = e.nextLocal
88 }
89 if e.nextLocal != nil {
90 e.nextLocal.prevLocal = e.prevLocal
91 }
92 // Update journal head/tail pointers.
93 if e.journal.heads[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020094 e.journal.heads[e.origin] = e.nextLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020095 }
96 if e.journal.tails[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020097 e.journal.tails[e.origin] = e.prevLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020098 }
99}
100
101// quota describes the quota policy for logging at a given DN.
102type quota struct {
103 // origin is the exact DN that this quota applies to.
104 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200105 // max is the maximum count of log entries permitted for this DN - ie, the maximum
106 // size of the local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200107 max uint64
108}
109
110// append adds an entry at the head of the global and local linked lists.
111func (j *journal) append(e *entry) {
112 j.mu.Lock()
113 defer j.mu.Unlock()
114
115 e.journal = j
Tim Windelschmidt01491a72025-07-23 21:49:11 +0200116 e.seqGlobal = j.seq
117 j.seq++
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200118
119 // Insert at head in global linked list, set pointers.
120 e.nextGlobal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200121 e.prevGlobal = j.tail
122 if j.tail != nil {
123 j.tail.nextGlobal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200124 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200125 j.tail = e
126 if j.head == nil {
127 j.head = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200128 }
129
130 // Create quota if necessary.
131 if _, ok := j.quota[e.origin]; !ok {
Tim Windelschmidt9e964882025-07-22 01:06:21 +0200132 j.quota[e.origin] = &quota{origin: e.origin, max: defaultDNQuota}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200133 }
134
135 // Insert at head in local linked list, calculate seqLocal, set pointers.
136 e.nextLocal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200137 e.prevLocal = j.tails[e.origin]
138 if j.tails[e.origin] != nil {
139 j.tails[e.origin].nextLocal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200140 e.seqLocal = e.prevLocal.seqLocal + 1
141 } else {
142 e.seqLocal = 0
143 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200144 j.tails[e.origin] = e
145 if j.heads[e.origin] == nil {
146 j.heads[e.origin] = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200147 }
148
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200149 // Apply quota to the local linked list that this entry got inserted to, ie. remove
150 // elements in excess of the quota.max count.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200151 quota := j.quota[e.origin]
Serge Bazanski8fab0142023-03-29 16:48:16 +0200152 count := (j.tails[e.origin].seqLocal - j.heads[e.origin].seqLocal) + 1
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200153 if count > quota.max {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200154 // Keep popping elements off the head of the local linked list until quota is not
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200155 // violated.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200156 left := count - quota.max
Serge Bazanski8fab0142023-03-29 16:48:16 +0200157 cur := j.heads[e.origin]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200158 for {
159 // This shouldn't happen if quota.max >= 1.
160 if cur == nil {
161 break
162 }
163 if left == 0 {
164 break
165 }
166 el := cur
167 cur = el.nextLocal
168 // Unlinking the entry unlinks it from both the global and local linked lists.
169 el.unlink()
170 left -= 1
171 }
172 }
173}