/
logger.go
170 lines (146 loc) · 6.3 KB
/
logger.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Package logger contains different methods to log.
package logger
import (
"fmt"
"io"
"os"
"runtime"
"strings"
gotesting "testing"
"time"
"github.com/gruntwork-io/terratest/modules/testing"
)
var (
// Default is the default logger that is used for the Logf function, if no one is provided. It uses the
// TerratestLogger to log messages. This can be overwritten to change the logging globally.
Default = New(terratestLogger{})
// Discard discards all logging.
Discard = New(discardLogger{})
// Terratest logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and
// information about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it
// logs to stdout immediately, rather than buffering all log output and only displaying it at the very end of the test.
// This is useful because:
//
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
// right away, rather than at the very end of the test run.
//
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
// output available.
//
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
// this log method, you get log output continuously.
//
Terratest = New(terratestLogger{})
// TestingT can be used to use Go's testing.T to log. If this is used, but no testing.T is provided, it will fallback
// to Default.
TestingT = New(testingT{})
)
type TestLogger interface {
Logf(t testing.TestingT, format string, args ...interface{})
}
type Logger struct {
l TestLogger
}
func New(l TestLogger) *Logger {
return &Logger{
l,
}
}
func (l *Logger) Logf(t testing.TestingT, format string, args ...interface{}) {
if tt, ok := t.(helper); ok {
tt.Helper()
}
// methods can be called on (typed) nil pointers. In this case, use the Default function to log. This enables the
// caller to do `var l *Logger` and then use the logger already.
if l == nil || l.l == nil {
Default.Logf(t, format, args...)
return
}
l.l.Logf(t, format, args...)
}
// helper is used to mark this library as a "helper", and thus not appearing in the line numbers. testing.T implements
// this interface, for example.
type helper interface {
Helper()
}
type discardLogger struct{}
func (_ discardLogger) Logf(_ testing.TestingT, format string, args ...interface{}) {}
type testingT struct{}
func (_ testingT) Logf(t testing.TestingT, format string, args ...interface{}) {
// this should never fail
tt, ok := t.(*gotesting.T)
if !ok {
// fallback
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
return
}
tt.Helper()
tt.Logf(format, args...)
return
}
type terratestLogger struct{}
func (_ terratestLogger) Logf(t testing.TestingT, format string, args ...interface{}) {
DoLog(t, 3, os.Stdout, fmt.Sprintf(format, args...))
}
// Deprecated: use Logger instead, as it provides more flexibility on logging.
// Logf logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and information
// about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it logs to stdout
// immediately, rather than buffering all log output and only displaying it at the very end of the test. This is useful
// because:
//
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
// right away, rather than at the very end of the test run.
//
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
// output available.
//
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
// this log method, you get log output continuously.
// Although t.Logf now supports streaming output since Go 1.14, this is kept for compatibility purposes.
func Logf(t testing.TestingT, format string, args ...interface{}) {
if tt, ok := t.(helper); ok {
tt.Helper()
}
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
}
// Log logs the given arguments to stdout, along with a timestamp and information about what test and file is doing the
// logging. This is an alternative to t.Logf that logs to stdout immediately, rather than buffering all log output and
// only displaying it at the very end of the test. See the Logf method for more info.
func Log(t testing.TestingT, args ...interface{}) {
if tt, ok := t.(helper); ok {
tt.Helper()
}
DoLog(t, 2, os.Stdout, args...)
}
// DoLog logs the given arguments to the given writer, along with a timestamp and information about what test and file is
// doing the logging.
func DoLog(t testing.TestingT, callDepth int, writer io.Writer, args ...interface{}) {
date := time.Now()
prefix := fmt.Sprintf("%s %s %s:", t.Name(), date.Format(time.RFC3339), CallerPrefix(callDepth+1))
allArgs := append([]interface{}{prefix}, args...)
fmt.Fprintln(writer, allArgs...)
}
// CallerPrefix returns the file and line number information about the methods that called this method, based on the current
// goroutine's stack. The argument callDepth is the number of stack frames to ascend, with 0 identifying the method
// that called CallerPrefix, 1 identifying the method that called that method, and so on.
//
// This code is adapted from testing.go, where it is in a private method called decorate.
func CallerPrefix(callDepth int) string {
_, file, line, ok := runtime.Caller(callDepth)
if ok {
// Truncate file name at last file name separator.
if index := strings.LastIndex(file, "/"); index >= 0 {
file = file[index+1:]
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
file = file[index+1:]
}
} else {
file = "???"
line = 1
}
return fmt.Sprintf("%s:%d", file, line)
}