1
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 package ktesting
38
39 import (
40 "fmt"
41 "strings"
42 "sync"
43 "time"
44
45 "github.com/go-logr/logr"
46
47 "k8s.io/klog/v2"
48 "k8s.io/klog/v2/internal/buffer"
49 "k8s.io/klog/v2/internal/dbg"
50 "k8s.io/klog/v2/internal/serialize"
51 "k8s.io/klog/v2/internal/severity"
52 "k8s.io/klog/v2/internal/verbosity"
53 )
54
55
56 type TL interface {
57 Helper()
58 Log(args ...interface{})
59 }
60
61
62
63 type NopTL struct{}
64
65 func (n NopTL) Helper() {}
66 func (n NopTL) Log(...interface{}) {}
67
68 var _ TL = NopTL{}
69
70
71 type BufferTL struct {
72 strings.Builder
73 }
74
75 func (n *BufferTL) Helper() {}
76 func (n *BufferTL) Log(args ...interface{}) {
77 n.Builder.WriteString(fmt.Sprintln(args...))
78 }
79
80 var _ TL = &BufferTL{}
81
82
83
84
85
86
87
88
89
90
91
92 func NewLogger(t TL, c *Config) logr.Logger {
93 l := tlogger{
94 shared: &tloggerShared{
95 t: t,
96 config: c,
97 },
98 }
99 if c.co.anyToString != nil {
100 l.shared.formatter.AnyToStringHook = c.co.anyToString
101 }
102
103 type testCleanup interface {
104 Cleanup(func())
105 Name() string
106 }
107
108
109
110 if tb, ok := t.(testCleanup); ok {
111 tb.Cleanup(l.shared.stop)
112 l.shared.testName = tb.Name()
113 }
114 return logr.New(l)
115 }
116
117
118
119 type Buffer interface {
120
121
122 String() string
123
124
125 Data() Log
126 }
127
128
129 type Log []LogEntry
130
131
132
133 func (l Log) DeepCopy() Log {
134 log := make(Log, 0, len(l))
135 log = append(log, l...)
136 return log
137 }
138
139
140 type LogEntry struct {
141
142 Timestamp time.Time
143
144
145 Type LogType
146
147
148 Prefix string
149
150
151 Message string
152
153
154 Verbosity int
155
156
157
158 Err error
159
160
161
162
163 WithKVList []interface{}
164
165
166
167 ParameterKVList []interface{}
168 }
169
170
171
172 type LogType string
173
174 const (
175
176 LogError = LogType("ERROR")
177
178
179 LogInfo = LogType("INFO")
180 )
181
182
183
184 type Underlier interface {
185
186
187 GetUnderlying() TL
188
189
190 GetBuffer() Buffer
191 }
192
193 type logBuffer struct {
194 mutex sync.Mutex
195 text strings.Builder
196 log Log
197 }
198
199 func (b *logBuffer) String() string {
200 b.mutex.Lock()
201 defer b.mutex.Unlock()
202 return b.text.String()
203 }
204
205 func (b *logBuffer) Data() Log {
206 b.mutex.Lock()
207 defer b.mutex.Unlock()
208 return b.log.DeepCopy()
209 }
210
211
212
213 type tloggerShared struct {
214
215 mutex sync.Mutex
216
217
218 t TL
219
220
221 stopTime time.Time
222
223
224
225
226
227
228
229
230
231
232
233 goroutineWarningDone bool
234
235 formatter serialize.Formatter
236 testName string
237 config *Config
238 buffer logBuffer
239 callDepth int
240 }
241
242
243
244 const stopGracePeriod = 10 * time.Second
245
246 func (ls *tloggerShared) stop() {
247 ls.mutex.Lock()
248 defer ls.mutex.Unlock()
249 ls.t = nil
250 ls.stopTime = time.Now()
251 }
252
253
254 type tlogger struct {
255 shared *tloggerShared
256 prefix string
257 values []interface{}
258 }
259
260
261
262 func (l tlogger) fallbackLogger() logr.Logger {
263 logger := klog.Background().WithValues(l.values...).WithName(l.shared.testName + " leaked goroutine")
264 if l.prefix != "" {
265 logger = logger.WithName(l.prefix)
266 }
267
268 logger = logger.WithCallDepth(l.shared.callDepth + 1)
269
270 if !l.shared.goroutineWarningDone {
271 duration := time.Since(l.shared.stopTime)
272 if duration > stopGracePeriod {
273
274 logger.WithCallDepth(1).Info("WARNING: test kept at least one goroutine running after test completion", "timeSinceCompletion", duration.Round(time.Second), "callstack", string(dbg.Stacks(false)))
275 l.shared.goroutineWarningDone = true
276 }
277 }
278 return logger
279 }
280
281 func (l tlogger) Init(info logr.RuntimeInfo) {
282 l.shared.callDepth = info.CallDepth
283 }
284
285 func (l tlogger) GetCallStackHelper() func() {
286 l.shared.mutex.Lock()
287 defer l.shared.mutex.Unlock()
288 if l.shared.t == nil {
289 return func() {}
290 }
291
292 return l.shared.t.Helper
293 }
294
295 func (l tlogger) Info(level int, msg string, kvList ...interface{}) {
296 l.shared.mutex.Lock()
297 defer l.shared.mutex.Unlock()
298 if l.shared.t == nil {
299 l.fallbackLogger().V(level).Info(msg, kvList...)
300 return
301 }
302
303 l.shared.t.Helper()
304 buf := buffer.GetBuffer()
305 l.shared.formatter.MergeAndFormatKVs(&buf.Buffer, l.values, kvList)
306 l.log(LogInfo, msg, level, buf, nil, kvList)
307 }
308
309 func (l tlogger) Enabled(level int) bool {
310 return l.shared.config.vstate.Enabled(verbosity.Level(level), 1)
311 }
312
313 func (l tlogger) Error(err error, msg string, kvList ...interface{}) {
314 l.shared.mutex.Lock()
315 defer l.shared.mutex.Unlock()
316 if l.shared.t == nil {
317 l.fallbackLogger().Error(err, msg, kvList...)
318 return
319 }
320
321 l.shared.t.Helper()
322 buf := buffer.GetBuffer()
323 if err != nil {
324 l.shared.formatter.KVFormat(&buf.Buffer, "err", err)
325 }
326 l.shared.formatter.MergeAndFormatKVs(&buf.Buffer, l.values, kvList)
327 l.log(LogError, msg, 0, buf, err, kvList)
328 }
329
330 func (l tlogger) log(what LogType, msg string, level int, buf *buffer.Buffer, err error, kvList []interface{}) {
331 l.shared.t.Helper()
332 s := severity.InfoLog
333 if what == LogError {
334 s = severity.ErrorLog
335 }
336 args := []interface{}{buf.SprintHeader(s, time.Now())}
337 if l.prefix != "" {
338 args = append(args, l.prefix+":")
339 }
340 args = append(args, msg)
341 if buf.Len() > 0 {
342
343 args = append(args, string(buf.Bytes()[1:]))
344 }
345 l.shared.t.Log(args...)
346
347 if !l.shared.config.co.bufferLogs {
348 return
349 }
350
351 l.shared.buffer.mutex.Lock()
352 defer l.shared.buffer.mutex.Unlock()
353
354
355 l.shared.buffer.text.WriteString(string(what))
356 for i := 1; i < len(args); i++ {
357 l.shared.buffer.text.WriteByte(' ')
358 l.shared.buffer.text.WriteString(args[i].(string))
359 }
360 lastArg := args[len(args)-1].(string)
361 if lastArg[len(lastArg)-1] != '\n' {
362 l.shared.buffer.text.WriteByte('\n')
363 }
364
365
366 l.shared.buffer.log = append(l.shared.buffer.log,
367 LogEntry{
368 Timestamp: time.Now(),
369 Type: what,
370 Prefix: l.prefix,
371 Message: msg,
372 Verbosity: level,
373 Err: err,
374 WithKVList: l.values,
375 ParameterKVList: kvList,
376 },
377 )
378 }
379
380
381
382
383 func (l tlogger) WithName(name string) logr.LogSink {
384 if len(l.prefix) > 0 {
385 l.prefix = l.prefix + "/"
386 }
387 l.prefix += name
388 return l
389 }
390
391 func (l tlogger) WithValues(kvList ...interface{}) logr.LogSink {
392 l.values = serialize.WithValues(l.values, kvList)
393 return l
394 }
395
396 func (l tlogger) GetUnderlying() TL {
397 return l.shared.t
398 }
399
400 func (l tlogger) GetBuffer() Buffer {
401 return &l.shared.buffer
402 }
403
404 var _ logr.LogSink = &tlogger{}
405 var _ logr.CallStackHelperLogSink = &tlogger{}
406 var _ Underlier = &tlogger{}
407
View as plain text