setuptest/logger.go (59 lines of code) (raw):
package setuptest
import (
"bytes"
"fmt"
"io"
"os"
"sync"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/testing"
)
var serializedLogger = func() *StreamLogger {
l := NewStreamLogger(os.Stdout)
l.outputProgress = false
return l
}()
// A StreamLogger is a logger that writes to a stream, such as stdout, a file or a memory buffer. It also keeps track of the number of
// log lines written, and can output a progress message every 50 lines.
type StreamLogger struct {
stream io.ReadWriter // The stream to write to
mu *sync.Mutex // A mutex to ensure only one thread writes to the stream at a time
logCount int // The number of log lines written
outputProgress bool // Whether or not to output a progress message every 50 lines
}
// NewMemoryLogger creates a new StreamLogger that writes to an in-memory buffer. This is useful for capturing logs in tests.
func NewMemoryLogger() *StreamLogger {
buff := new(bytes.Buffer)
return NewStreamLogger(buff)
}
// NewStreamLogger creates a new StreamLogger that writes to the given stream.
func NewStreamLogger(stream io.ReadWriter) *StreamLogger {
return &StreamLogger{
stream: stream,
mu: new(sync.Mutex),
outputProgress: false,
}
}
// Logf logs the given arguments to the given writer, along with a prefix of the test name.
func (s *StreamLogger) Logf(t testing.TestingT, format string, args ...interface{}) {
// Sprintf removed as we don't want the prefixes to the log lines
// log := fmt.Sprintf(format, args...)
doLog(t, s.stream, args...)
s.logCount++
if s.outputProgress && s.logCount%50 == 0 {
logger.Log(t, fmt.Sprintf("logging sample: %s", args))
}
}
// The PipeFrom function is a method of the StreamLogger struct. It takes a pointer to another StreamLogger object as its input parameter and returns an error.
// Inside the function, a mutex lock is acquired to ensure that the function is thread-safe.
// The PipeFrom function is useful when you want to redirect the output of one logger to another logger.
// For example, if you have a logger that writes to the console and another logger that writes to a file, you can use PipeFrom to redirect the console logger's output to the file logger:
func (s *StreamLogger) PipeFrom(srcLogger *StreamLogger) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := io.Copy(s.stream, srcLogger.stream)
return err
}
func (s *StreamLogger) Close() error {
defer func() {
c, ok := s.stream.(io.Closer)
if ok {
_ = c.Close()
}
}()
return serializedLogger.PipeFrom(s)
}
// doLog logs the given arguments to the given writer, along with a prefix of the test name.
func doLog(t testing.TestingT, writer io.Writer, args ...interface{}) {
// date := time.Now()
prefix := fmt.Sprintf("%s:", t.Name())
allArgs := append([]interface{}{prefix}, args...)
fmt.Fprintln(writer, allArgs...)
}