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