Skip to content

Commit

Permalink
logger reintroduced
Browse files Browse the repository at this point in the history
mkyc committed Nov 26, 2020
1 parent aadc1ed commit dab20d9
Showing 3 changed files with 143 additions and 21 deletions.
108 changes: 108 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package terra

import "log"

type Logger interface {

// Trace arguments are handled in the manner of fmt.Printf.
Trace(format string, v ...interface{})

// Debug arguments are handled in the manner of fmt.Printf.
Debug(format string, v ...interface{})

// Info arguments are handled in the manner of fmt.Printf.
Info(format string, v ...interface{})

// Warn arguments are handled in the manner of fmt.Printf.
Warn(format string, v ...interface{})

// Error arguments are handled in the manner of fmt.Printf.
Error(format string, v ...interface{})

// Fatal arguments are handled in the manner of fmt.Printf.
Fatal(format string, v ...interface{})

// Panic arguments are handled in the manner of fmt.Printf.
Panic(format string, v ...interface{})
}

type DefaultLogger struct {
l Logger
}

// Trace calls log.Printf or log.Println (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Trace(format string, v ...interface{}) {
if len(v) > 0 {
log.Printf("[Trace] "+format, v...)
} else {
log.Println("[Trace] " + format)
}
}

// Debug calls log.Printf or log.Println (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Debug(format string, v ...interface{}) {
if len(v) > 0 {
log.Printf("[Debug] "+format, v...)
} else {
log.Println("[Debug] " + format)
}
}

// Info calls log.Printf or log.Println (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Info(format string, v ...interface{}) {
if len(v) > 0 {
log.Printf("[Info] "+format, v...)
} else {
log.Println("[Info] " + format)
}
}

// Warn calls log.Printf or log.Println (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Warn(format string, v ...interface{}) {
if len(v) > 0 {
log.Printf("[Warn] "+format, v...)
} else {
log.Println("[Warn] " + format)
}
}

// Error calls log.Printf or log.Println (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Error(format string, v ...interface{}) {
if len(v) > 0 {
log.Printf("[Error] "+format, v...)
} else {
log.Println("[Error] " + format)
}
}

// Fatal calls log.Fatalf or log.Fatalln (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Fatal(format string, v ...interface{}) {
if len(v) > 0 {
log.Fatalf("[Trace] "+format, v...)
} else {
log.Fatalln("[Trace] " + format)
}
}

// Panic calls log.Panicf or log.Panicln (if there is no
// additional arguments) to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func (DefaultLogger) Panic(format string, v ...interface{}) {
if len(v) > 0 {
log.Panicf("[Trace] "+format, v...)
} else {
log.Panicln("[Trace] " + format)
}
}
11 changes: 10 additions & 1 deletion options.go
Original file line number Diff line number Diff line change
@@ -68,7 +68,7 @@ type Options struct {
Parallelism int // Set the parallelism setting for Terraform
PlanFilePath string // The path to output a plan file to (for the plan command) or read one from (for the apply command)
StateFilePath string // The path to state file
//TODO consider adding logger back
Logger Logger
//TODO consider adding ssh agent back
}

@@ -98,6 +98,10 @@ func GetCommonOptions(options *Options, args ...string) (*Options, []string) {
args = append(args, fmt.Sprintf("--parallelism=%d", options.Parallelism))
}

if options.Logger == nil {
options.Logger = &DefaultLogger{}
}

return options, args
}

@@ -106,6 +110,7 @@ func GetCommonOptions(options *Options, args ...string) (*Options, []string) {
// testing, and are known to self resolve upon retrying.
// This will fail the test if there are any errors in the cloning process.
func WithDefaultRetryableErrors(originalOptions *Options) (*Options, error) {
//TODO why cloning ... ?
newOptions, err := originalOptions.Clone()
if err != nil {
return nil, err
@@ -123,5 +128,9 @@ func WithDefaultRetryableErrors(originalOptions *Options) (*Options, error) {
newOptions.MaxRetries = 3
newOptions.TimeBetweenRetries = 5 * time.Second

if newOptions.Logger == nil {
newOptions.Logger = &DefaultLogger{}
}

return newOptions, nil
}
45 changes: 25 additions & 20 deletions run.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/exec"
"regexp"
@@ -20,7 +19,7 @@ type Command struct {
WorkingDir string // The working directory
Env map[string]string // Additional environment variables to set
// Use the specified logger for the command's output. Use logger.Discard to not print the output while executing the command.
//TODO consider adding logger back
Logger Logger
}

// RunTerraformCommandE runs terraform with the given arguments and options and return stdout/stderr.
@@ -29,7 +28,7 @@ func RunTerraformCommandE(additionalOptions *Options, additionalArgs ...string)

cmd := generateCommand(options, args...)
description := fmt.Sprintf("%s %v", options.TerraformBinary, args)
return DoWithRetryableErrorsE(description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, func() (string, error) {
return DoWithRetryableErrorsE(description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, options.Logger, func() (string, error) {
return RunCommandAndGetOutputE(cmd)
})
}
@@ -41,7 +40,7 @@ func RunTerraformCommandAndGetStdoutE(additionalOptions *Options, additionalArgs

cmd := generateCommand(options, args...)
description := fmt.Sprintf("%s %v", options.TerraformBinary, args)
return DoWithRetryableErrorsE(description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, func() (string, error) {
return DoWithRetryableErrorsE(description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, options.Logger, func() (string, error) {
return RunCommandAndGetStdOutE(cmd)
})
}
@@ -52,6 +51,7 @@ func generateCommand(options *Options, args ...string) Command {
Args: args,
WorkingDir: options.TerraformDir,
Env: options.EnvVars,
Logger: options.Logger,
}
return cmd
}
@@ -90,7 +90,7 @@ func RunCommandAndGetStdOutE(command Command) (string, error) {
// stdout and stderr of that command will also be printed to the stdout and stderr of this Go program to make debugging
// easier.
func runCommand(command Command) (*output, error) {
log.Printf("Running command %s with args %s\n", command.Command, command.Args)
command.Logger.Info("Running command %s with args %s\n", command.Command, command.Args)

cmd := exec.Command(command.Command, command.Args...)
cmd.Dir = command.WorkingDir
@@ -112,7 +112,7 @@ func runCommand(command Command) (*output, error) {
return nil, err
}

output, err := readStdoutAndStderr(stdout, stderr)
output, err := readStdoutAndStderr(command, stdout, stderr)
if err != nil {
return output, err
}
@@ -122,7 +122,7 @@ func runCommand(command Command) (*output, error) {

// This function captures stdout and stderr into the given variables while still printing it to the stdout and stderr
// of this Go program
func readStdoutAndStderr(stdout, stderr io.ReadCloser) (*output, error) {
func readStdoutAndStderr(command Command, stdout, stderr io.ReadCloser) (*output, error) {
out := newOutput()
stdoutReader := bufio.NewReader(stdout)
stderrReader := bufio.NewReader(stderr)
@@ -133,11 +133,11 @@ func readStdoutAndStderr(stdout, stderr io.ReadCloser) (*output, error) {
var stdoutErr, stderrErr error
go func() {
defer wg.Done()
stdoutErr = readData(stdoutReader, out.stdout)
stdoutErr = readData(command, false, stdoutReader, out.stdout)
}()
go func() {
defer wg.Done()
stderrErr = readData(stderrReader, out.stderr)
stderrErr = readData(command, true, stderrReader, out.stderr)
}()
wg.Wait()

@@ -159,7 +159,7 @@ func formatEnvVars(command Command) []string {
return env
}

func readData(reader *bufio.Reader, writer io.StringWriter) error {
func readData(command Command, isStderr bool, reader *bufio.Reader, writer io.StringWriter) error {
var line string
var readErr error
for {
@@ -177,7 +177,12 @@ func readData(reader *bufio.Reader, writer io.StringWriter) error {
break
}

log.Println(line)
if isStderr {
command.Logger.Warn(line)
} else {
command.Logger.Info(line)
}

if _, err := writer.WriteString(line); err != nil {
return err
}
@@ -197,7 +202,7 @@ func readData(reader *bufio.Reader, writer io.StringWriter) error {
// matches any of the regular expressions in the specified retryableErrors map. If there is a match, sleep for
// sleepBetweenRetries, and retry the specified action, up to a maximum of maxRetries retries. If there is no match,
// return that error immediately, wrapped in a FatalError. If maxRetries is exceeded, return a MaxRetriesExceeded error.
func DoWithRetryableErrorsE(actionDescription string, retryableErrors map[string]string, maxRetries int, sleepBetweenRetries time.Duration, action func() (string, error)) (string, error) {
func DoWithRetryableErrorsE(actionDescription string, retryableErrors map[string]string, maxRetries int, sleepBetweenRetries time.Duration, logger Logger, action func() (string, error)) (string, error) {
retryableErrorsRegexp := map[*regexp.Regexp]string{}
for errorStr, errorMessage := range retryableErrors {
errorRegex, err := regexp.Compile(errorStr)
@@ -207,15 +212,15 @@ func DoWithRetryableErrorsE(actionDescription string, retryableErrors map[string
retryableErrorsRegexp[errorRegex] = errorMessage
}

return DoWithRetryE(actionDescription, maxRetries, sleepBetweenRetries, func() (string, error) {
return DoWithRetryE(actionDescription, maxRetries, sleepBetweenRetries, logger, func() (string, error) {
output, err := action()
if err == nil {
return output, nil
}

for errorRegexp, errorMessage := range retryableErrorsRegexp {
if errorRegexp.MatchString(output) || errorRegexp.MatchString(err.Error()) {
log.Printf("'%s' failed with the error '%s' but this error was expected and warrants a retry. Further details: %s\n", actionDescription, err.Error(), errorMessage)
logger.Warn("'%s' failed with the error '%s' but this error was expected and warrants a retry. Further details: %s\n", actionDescription, err.Error(), errorMessage)
return output, err
}
}
@@ -227,32 +232,32 @@ func DoWithRetryableErrorsE(actionDescription string, retryableErrors map[string
// DoWithRetryE runs the specified action. If it returns a string, return that string. If it returns a FatalError, return that error
// immediately. If it returns any other type of error, sleep for sleepBetweenRetries and try again, up to a maximum of
// maxRetries retries. If maxRetries is exceeded, return a MaxRetriesExceeded error.
func DoWithRetryE(actionDescription string, maxRetries int, sleepBetweenRetries time.Duration, action func() (string, error)) (string, error) {
out, err := DoWithRetryInterfaceE(actionDescription, maxRetries, sleepBetweenRetries, func() (interface{}, error) { return action() })
func DoWithRetryE(actionDescription string, maxRetries int, sleepBetweenRetries time.Duration, logger Logger, action func() (string, error)) (string, error) {
out, err := DoWithRetryInterfaceE(actionDescription, maxRetries, sleepBetweenRetries, logger, func() (interface{}, error) { return action() })
return out.(string), err
}

// DoWithRetryInterfaceE runs the specified action. If it returns a value, return that value. If it returns a FatalError, return that error
// immediately. If it returns any other type of error, sleep for sleepBetweenRetries and try again, up to a maximum of
// maxRetries retries. If maxRetries is exceeded, return a MaxRetriesExceeded error.
func DoWithRetryInterfaceE(actionDescription string, maxRetries int, sleepBetweenRetries time.Duration, action func() (interface{}, error)) (interface{}, error) {
func DoWithRetryInterfaceE(actionDescription string, maxRetries int, sleepBetweenRetries time.Duration, logger Logger, action func() (interface{}, error)) (interface{}, error) {
var output interface{}
var err error

for i := 0; i <= maxRetries; i++ {
log.Println(actionDescription)
logger.Info(actionDescription)

output, err = action()
if err == nil {
return output, nil
}

if _, isFatalErr := err.(FatalError); isFatalErr {
log.Printf("Returning due to fatal error: %v\n", err)
logger.Error("Returning due to fatal error: %v\n", err)
return output, err
}

log.Printf("%s returned an error: %s. Sleeping for %s and will try again.\n", actionDescription, err.Error(), sleepBetweenRetries)
logger.Warn("%s returned an error: %s. Sleeping for %s and will try again.\n", actionDescription, err.Error(), sleepBetweenRetries)
time.Sleep(sleepBetweenRetries)
}

0 comments on commit dab20d9

Please sign in to comment.