1 package logs
2
3 import (
4 "bytes"
5 "io"
6 "os"
7 "testing"
8 "time"
9
10 "github.com/sirupsen/logrus"
11 )
12
13 const msgErr = `"level":"error"`
14
15 func TestLoggingToFile(t *testing.T) {
16 l := runLogForwarding(t)
17
18 msg := `"level":"info","msg":"kitten"`
19 logToLogWriter(t, l, msg)
20 finish(t, l)
21 check(t, l, msg, msgErr)
22 }
23
24 func TestLogForwardingDoesNotStopOnJsonDecodeErr(t *testing.T) {
25 l := runLogForwarding(t)
26
27 logToLogWriter(t, l, `"invalid-json-with-kitten"`)
28 checkWait(t, l, msgErr, "")
29
30 truncateLogFile(t, l.file)
31
32 msg := `"level":"info","msg":"puppy"`
33 logToLogWriter(t, l, msg)
34 finish(t, l)
35 check(t, l, msg, msgErr)
36 }
37
38 func TestLogForwardingDoesNotStopOnLogLevelParsingErr(t *testing.T) {
39 l := runLogForwarding(t)
40
41 msg := `"level":"alert","msg":"puppy"`
42 logToLogWriter(t, l, msg)
43 checkWait(t, l, msgErr, msg)
44
45 truncateLogFile(t, l.file)
46
47 msg = `"level":"info","msg":"puppy"`
48 logToLogWriter(t, l, msg)
49 finish(t, l)
50 check(t, l, msg, msgErr)
51 }
52
53 func TestLogForwardingStopsAfterClosingTheWriter(t *testing.T) {
54 l := runLogForwarding(t)
55
56 msg := `"level":"info","msg":"sync"`
57 logToLogWriter(t, l, msg)
58
59
60 l.w.Close()
61 select {
62 case <-l.done:
63 case <-time.After(10 * time.Second):
64 t.Fatal("log forwarding did not stop after closing the pipe")
65 }
66
67 check(t, l, msg, msgErr)
68 }
69
70 func logToLogWriter(t *testing.T, l *log, message string) {
71 t.Helper()
72 _, err := l.w.Write([]byte("{" + message + "}\n"))
73 if err != nil {
74 t.Fatalf("failed to write %q to log writer: %v", message, err)
75 }
76 }
77
78 type log struct {
79 w io.WriteCloser
80 file *os.File
81 done chan error
82 }
83
84 func runLogForwarding(t *testing.T) *log {
85 t.Helper()
86 logR, logW, err := os.Pipe()
87 if err != nil {
88 t.Fatal(err)
89 }
90 t.Cleanup(func() {
91 logR.Close()
92 logW.Close()
93 })
94
95 tempFile, err := os.CreateTemp("", "")
96 if err != nil {
97 t.Fatal(err)
98 }
99 t.Cleanup(func() {
100 tempFile.Close()
101 os.Remove(tempFile.Name())
102 })
103
104 logrus.SetOutput(tempFile)
105 logrus.SetFormatter(&logrus.JSONFormatter{})
106 doneForwarding := ForwardLogs(logR)
107
108 return &log{w: logW, done: doneForwarding, file: tempFile}
109 }
110
111 func finish(t *testing.T, l *log) {
112 t.Helper()
113 l.w.Close()
114 if err := <-l.done; err != nil {
115 t.Fatalf("ForwardLogs: %v", err)
116 }
117 }
118
119 func truncateLogFile(t *testing.T, file *os.File) {
120 t.Helper()
121
122 err := file.Truncate(0)
123 if err != nil {
124 t.Fatalf("failed to truncate log file: %v", err)
125 }
126 }
127
128
129 func check(t *testing.T, l *log, txt, notxt string) {
130 t.Helper()
131 contents, err := os.ReadFile(l.file.Name())
132 if err != nil {
133 t.Fatal(err)
134 }
135 if txt != "" && !bytes.Contains(contents, []byte(txt)) {
136 t.Fatalf("%s does not contain %s", contents, txt)
137 }
138 if notxt != "" && bytes.Contains(contents, []byte(notxt)) {
139 t.Fatalf("%s does contain %s", contents, notxt)
140 }
141 }
142
143
144
145 func checkWait(t *testing.T, l *log, txt string, notxt string) {
146 t.Helper()
147 const (
148 delay = 100 * time.Millisecond
149 iter = 3
150 )
151 for i := 0; ; i++ {
152 st, err := l.file.Stat()
153 if err != nil {
154 t.Fatal(err)
155 }
156 if st.Size() > 0 {
157 break
158 }
159 if i == iter {
160 t.Fatalf("waited %s for file %s to be non-empty but it still is", iter*delay, l.file.Name())
161 }
162 time.Sleep(delay)
163 }
164
165 check(t, l, txt, notxt)
166 }
167
View as plain text