blob: ecc3253e59c378576339d407495aab02f9a1d96a [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
6import (
7 "fmt"
8 "strings"
9 "testing"
10 "time"
Serge Bazanski3c5d0632024-09-12 10:49:12 +000011
12 "source.monogon.dev/go/logging"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020013)
14
Serge Bazanski12971d62020-11-17 12:12:58 +010015func expect(tree *LogTree, t *testing.T, dn DN, entries ...string) string {
16 t.Helper()
17 res, err := tree.Read(dn, WithChildren(), WithBacklog(BacklogAllAvailable))
18 if err != nil {
19 t.Fatalf("Read: %v", err)
20 }
Serge Bazanski8fab0142023-03-29 16:48:16 +020021 defer res.Close()
Serge Bazanski12971d62020-11-17 12:12:58 +010022 if want, got := len(entries), len(res.Backlog); want != got {
23 t.Fatalf("wanted %v backlog entries, got %v", want, got)
24 }
25 got := make(map[string]bool)
26 for _, entry := range res.Backlog {
27 if entry.Leveled != nil {
28 got[entry.Leveled.MessagesJoined()] = true
29 }
30 if entry.Raw != nil {
31 got[entry.Raw.Data] = true
32 }
33 }
34 for _, entry := range entries {
35 if !got[entry] {
36 return fmt.Sprintf("missing entry %q", entry)
37 }
38 }
39 return ""
40}
41
Serge Bazanski8fab0142023-03-29 16:48:16 +020042func readBacklog(tree *LogTree, t *testing.T, dn DN, backlog int, recursive bool) []string {
43 t.Helper()
44 opts := []LogReadOption{
45 WithBacklog(backlog),
46 }
47 if recursive {
48 opts = append(opts, WithChildren())
49 }
50 res, err := tree.Read(dn, opts...)
51 if err != nil {
52 t.Fatalf("Read: %v", err)
53 }
54 defer res.Close()
55
56 var lines []string
57 for _, e := range res.Backlog {
Tim Windelschmidte1acfe32024-04-18 23:02:22 +020058 lines = append(lines, e.Leveled.Messages()...)
Serge Bazanski8fab0142023-03-29 16:48:16 +020059 }
60 return lines
61}
62
Serge Bazanski12971d62020-11-17 12:12:58 +010063func TestMultiline(t *testing.T) {
64 tree := New()
65 // Two lines in a single message.
66 tree.MustLeveledFor("main").Info("foo\nbar")
67 // Two lines in a single message with a hanging newline that should get stripped.
68 tree.MustLeveledFor("main").Info("one\ntwo\n")
69
70 if res := expect(tree, t, "main", "foo\nbar", "one\ntwo"); res != "" {
71 t.Errorf("retrieval at main failed: %s", res)
72 }
73}
74
Serge Bazanski8fab0142023-03-29 16:48:16 +020075func TestBacklogAll(t *testing.T) {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020076 tree := New()
77 tree.MustLeveledFor("main").Info("hello, main!")
78 tree.MustLeveledFor("main.foo").Info("hello, main.foo!")
79 tree.MustLeveledFor("main.bar").Info("hello, main.bar!")
80 tree.MustLeveledFor("aux").Info("hello, aux!")
Serge Bazanskif68153c2020-10-26 13:54:37 +010081 // No newline at the last entry - shouldn't get propagated to the backlog.
82 fmt.Fprintf(tree.MustRawFor("aux.process"), "processing foo\nprocessing bar\nbaz")
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020083
Serge Bazanski12971d62020-11-17 12:12:58 +010084 if res := expect(tree, t, "main", "hello, main!", "hello, main.foo!", "hello, main.bar!"); res != "" {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020085 t.Errorf("retrieval at main failed: %s", res)
86 }
Serge Bazanski12971d62020-11-17 12:12:58 +010087 if res := expect(tree, t, "", "hello, main!", "hello, main.foo!", "hello, main.bar!", "hello, aux!", "processing foo", "processing bar"); res != "" {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020088 t.Errorf("retrieval at root failed: %s", res)
89 }
Serge Bazanski12971d62020-11-17 12:12:58 +010090 if res := expect(tree, t, "aux", "hello, aux!", "processing foo", "processing bar"); res != "" {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020091 t.Errorf("retrieval at aux failed: %s", res)
92 }
93}
94
Serge Bazanski8fab0142023-03-29 16:48:16 +020095func TestBacklogExact(t *testing.T) {
96 tree := New()
97 tree.MustLeveledFor("main").Info("hello, main!")
98 tree.MustLeveledFor("main.foo").Info("hello, main.foo!")
99 tree.MustLeveledFor("main.bar").Info("hello, main.bar!")
100 tree.MustLeveledFor("main.bar.chatty").Info("hey there how are you")
101 tree.MustLeveledFor("main.bar.quiet").Info("fine how are you")
102 tree.MustLeveledFor("main.bar.chatty").Info("i've been alright myself")
103 tree.MustLeveledFor("main.bar.chatty").Info("but to tell you honestly...")
104 tree.MustLeveledFor("main.bar.chatty").Info("i feel like i'm stuck?")
105 tree.MustLeveledFor("main.bar.quiet").Info("mhm")
106 tree.MustLeveledFor("main.bar.chatty").Info("like you know what i'm saying, stuck in like")
107 tree.MustLeveledFor("main.bar.chatty").Info("like a go test?")
108 tree.MustLeveledFor("main.bar.quiet").Info("yeah totally")
109 tree.MustLeveledFor("main.bar.chatty").Info("it's hard to put my finger on it")
110 tree.MustLeveledFor("main.bar.chatty").Info("anyway, how's the wife doing?")
111
112 check := func(a []string, b ...string) {
113 t.Helper()
114 if len(a) != len(b) {
115 t.Errorf("Legth mismatch: wanted %d, got %d", len(b), len(a))
116 }
117 count := len(a)
118 if len(b) < count {
119 count = len(b)
120 }
121 for i := 0; i < count; i++ {
122 if want, got := b[i], a[i]; want != got {
123 t.Errorf("Message %d: wanted %q, got %q", i, want, got)
124 }
125 }
126 }
127
128 check(readBacklog(tree, t, "main", 3, true), "yeah totally", "it's hard to put my finger on it", "anyway, how's the wife doing?")
129 check(readBacklog(tree, t, "main.foo", 3, false), "hello, main.foo!")
130 check(readBacklog(tree, t, "main.bar.quiet", 2, true), "mhm", "yeah totally")
131}
132
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200133func TestStream(t *testing.T) {
134 tree := New()
135 tree.MustLeveledFor("main").Info("hello, backlog")
Serge Bazanskif68153c2020-10-26 13:54:37 +0100136 fmt.Fprintf(tree.MustRawFor("main.process"), "hello, raw backlog\n")
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200137
Serge Bazanskif68153c2020-10-26 13:54:37 +0100138 res, err := tree.Read("", WithBacklog(BacklogAllAvailable), WithChildren(), WithStream())
139 if err != nil {
140 t.Fatalf("Read: %v", err)
141 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200142 defer res.Close()
Serge Bazanskif68153c2020-10-26 13:54:37 +0100143 if want, got := 2, len(res.Backlog); want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200144 t.Errorf("wanted %d backlog item, got %d", want, got)
145 }
146
147 tree.MustLeveledFor("main").Info("hello, stream")
Serge Bazanskif68153c2020-10-26 13:54:37 +0100148 fmt.Fprintf(tree.MustRawFor("main.raw"), "hello, raw stream\n")
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200149
Serge Bazanskif68153c2020-10-26 13:54:37 +0100150 entries := make(map[string]bool)
151 timeout := time.After(time.Second * 1)
152 for {
153 done := false
154 select {
155 case <-timeout:
156 done = true
157 case p := <-res.Stream:
158 if p.Leveled != nil {
Serge Bazanski12971d62020-11-17 12:12:58 +0100159 entries[p.Leveled.MessagesJoined()] = true
Serge Bazanskif68153c2020-10-26 13:54:37 +0100160 }
161 if p.Raw != nil {
162 entries[p.Raw.Data] = true
163 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200164 }
Serge Bazanskif68153c2020-10-26 13:54:37 +0100165 if done {
166 break
167 }
168 }
169 if entry := "hello, stream"; !entries[entry] {
170 t.Errorf("Missing entry %q", entry)
171 }
172 if entry := "hello, raw stream"; !entries[entry] {
173 t.Errorf("Missing entry %q", entry)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200174 }
175}
176
177func TestVerbose(t *testing.T) {
178 tree := New()
179
180 tree.MustLeveledFor("main").V(10).Info("this shouldn't get logged")
181
Serge Bazanskif68153c2020-10-26 13:54:37 +0100182 reader, err := tree.Read("", WithBacklog(BacklogAllAvailable), WithChildren())
183 if err != nil {
184 t.Fatalf("Read: %v", err)
185 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200186 if want, got := 0, len(reader.Backlog); want != got {
187 t.Fatalf("expected nothing to be logged, got %+v", reader.Backlog)
188 }
189
190 tree.SetVerbosity("main", 10)
191 tree.MustLeveledFor("main").V(10).Info("this should get logged")
192
Serge Bazanskif68153c2020-10-26 13:54:37 +0100193 reader, err = tree.Read("", WithBacklog(BacklogAllAvailable), WithChildren())
194 if err != nil {
195 t.Fatalf("Read: %v", err)
196 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200197 if want, got := 1, len(reader.Backlog); want != got {
198 t.Fatalf("expected %d entries to get logged, got %d", want, got)
199 }
200}
201
202func TestMetadata(t *testing.T) {
203 tree := New()
204 tree.MustLeveledFor("main").Error("i am an error")
205 tree.MustLeveledFor("main").Warning("i am a warning")
206 tree.MustLeveledFor("main").Info("i am informative")
207 tree.MustLeveledFor("main").V(0).Info("i am a zero-level debug")
208
Serge Bazanskif68153c2020-10-26 13:54:37 +0100209 reader, err := tree.Read("", WithChildren(), WithBacklog(BacklogAllAvailable))
210 if err != nil {
211 t.Fatalf("Read: %v", err)
212 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200213 if want, got := 4, len(reader.Backlog); want != got {
214 t.Fatalf("expected %d entries, got %d", want, got)
215 }
216
217 for _, te := range []struct {
218 ix int
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000219 severity logging.Severity
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200220 message string
221 }{
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000222 {0, logging.ERROR, "i am an error"},
223 {1, logging.WARNING, "i am a warning"},
224 {2, logging.INFO, "i am informative"},
225 {3, logging.INFO, "i am a zero-level debug"},
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200226 } {
227 p := reader.Backlog[te.ix]
Serge Bazanskif68153c2020-10-26 13:54:37 +0100228 if want, got := te.severity, p.Leveled.Severity(); want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200229 t.Errorf("wanted element %d to have severity %s, got %s", te.ix, want, got)
230 }
Serge Bazanski12971d62020-11-17 12:12:58 +0100231 if want, got := te.message, p.Leveled.MessagesJoined(); want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200232 t.Errorf("wanted element %d to have message %q, got %q", te.ix, want, got)
233 }
Serge Bazanskif68153c2020-10-26 13:54:37 +0100234 if want, got := "logtree_test.go", strings.Split(p.Leveled.Location(), ":")[0]; want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200235 t.Errorf("wanted element %d to have file %q, got %q", te.ix, want, got)
236 }
237 }
238}
239
240func TestSeverity(t *testing.T) {
241 tree := New()
242 tree.MustLeveledFor("main").Error("i am an error")
243 tree.MustLeveledFor("main").Warning("i am a warning")
244 tree.MustLeveledFor("main").Info("i am informative")
245 tree.MustLeveledFor("main").V(0).Info("i am a zero-level debug")
246
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000247 reader, err := tree.Read("main", WithBacklog(BacklogAllAvailable), LeveledWithMinimumSeverity(logging.WARNING))
Serge Bazanskif68153c2020-10-26 13:54:37 +0100248 if err != nil {
249 t.Fatalf("Read: %v", err)
250 }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200251 if want, got := 2, len(reader.Backlog); want != got {
252 t.Fatalf("wanted %d entries, got %d", want, got)
253 }
Serge Bazanski12971d62020-11-17 12:12:58 +0100254 if want, got := "i am an error", reader.Backlog[0].Leveled.MessagesJoined(); want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200255 t.Fatalf("wanted entry %q, got %q", want, got)
256 }
Serge Bazanski12971d62020-11-17 12:12:58 +0100257 if want, got := "i am a warning", reader.Backlog[1].Leveled.MessagesJoined(); want != got {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200258 t.Fatalf("wanted entry %q, got %q", want, got)
259 }
260}
Serge Bazanskid9775a62022-02-15 13:28:55 +0100261
262func TestAddedStackDepth(t *testing.T) {
263 tree := New()
264 helper := func(msg string) {
265 tree.MustLeveledFor("main").WithAddedStackDepth(1).Infof("oh no: %s", msg)
266 }
267
268 // The next three lines are tested to be next to each other.
269 helper("it failed")
270 tree.MustLeveledFor("main").Infof("something else")
271
272 reader, err := tree.Read("main", WithBacklog(BacklogAllAvailable))
273 if err != nil {
274 t.Fatalf("Read: %v", err)
275 }
276 if want, got := 2, len(reader.Backlog); want != got {
277 t.Fatalf("wanted %d entries, got %d", want, got)
278 }
279 if want, got := "oh no: it failed", reader.Backlog[0].Leveled.MessagesJoined(); want != got {
280 t.Errorf("wanted entry %q, got %q", want, got)
281 }
282 if want, got := "something else", reader.Backlog[1].Leveled.MessagesJoined(); want != got {
283 t.Errorf("wanted entry %q, got %q", want, got)
284 }
285 if first, second := reader.Backlog[0].Leveled.line, reader.Backlog[1].Leveled.line; first+1 != second {
286 t.Errorf("first entry at %d, second at %d, wanted one after the other", first, second)
287 }
288}
Serge Bazanski367ee272023-03-16 17:50:39 +0100289
290func TestLogEntry_ConciseString(t *testing.T) {
291 trim := func(s string) string {
292 return strings.Trim(s, "\n")
293 }
294 for i, te := range []struct {
295 entry *LogEntry
296 maxWidth int
297 want string
298 }{
299 {
300 &LogEntry{
301 Leveled: &LeveledPayload{
302 messages: []string{"Hello there!"},
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000303 severity: logging.WARNING,
Serge Bazanski367ee272023-03-16 17:50:39 +0100304 },
305 DN: "root.role.kubernetes.run.kubernetes.apiserver",
306 },
307 120,
308 " k8s apiserver W Hello there!",
309 },
310 {
311 &LogEntry{
312 Leveled: &LeveledPayload{
313 messages: []string{"Hello there!", "I am multiline."},
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000314 severity: logging.WARNING,
Serge Bazanski367ee272023-03-16 17:50:39 +0100315 },
316 DN: "root.role.kubernetes.run.kubernetes.apiserver",
317 },
318 120,
319 trim(`
320 k8s apiserver W Hello there!
321 | I am multiline.
322`),
323 },
324 {
325 &LogEntry{
326 Leveled: &LeveledPayload{
327 messages: []string{"Hello there! I am a very long string, and I will get wrapped to 120 columns because that's just how life is for long strings."},
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000328 severity: logging.WARNING,
Serge Bazanski367ee272023-03-16 17:50:39 +0100329 },
330 DN: "root.role.kubernetes.run.kubernetes.apiserver",
331 },
332 120,
333 trim(`
334 k8s apiserver W Hello there! I am a very long string, and I will get wrapped to 120 columns because that's just
335 | how life is for long strings.
336`),
337 },
338 {
339 &LogEntry{
340 Leveled: &LeveledPayload{
341 messages: []string{"Hello there!"},
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000342 severity: logging.WARNING,
Serge Bazanski367ee272023-03-16 17:50:39 +0100343 },
344 DN: "root.role.kubernetes.run.kubernetes.apiserver",
345 },
346 60,
347 trim(`
348 k8s apiserver W Hello there!
349`),
350 },
351 {
352 &LogEntry{
353 Leveled: &LeveledPayload{
354 messages: []string{"Hello there!"},
Serge Bazanski3c5d0632024-09-12 10:49:12 +0000355 severity: logging.WARNING,
Serge Bazanski367ee272023-03-16 17:50:39 +0100356 },
357 DN: "root.role.kubernetes.run.kubernetes.apiserver",
358 },
359 40,
360 "W Hello there!",
361 },
362 } {
363 got := te.entry.ConciseString(MetropolisShortenDict, te.maxWidth)
364 for _, line := range strings.Split(got, "\n") {
365 if want, got := te.maxWidth, len(line); got > want {
366 t.Errorf("Case %d, line %q too long (%d bytes, wanted at most %d)", i, line, got, want)
367 }
368 }
369 if te.want != got {
370 t.Errorf("Case %d, message diff", i)
371 t.Logf("Wanted:\n%s", te.want)
372 t.Logf("Got:\n%s", got)
373 }
374 }
375}