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  }