Skip to content

Commit

Permalink
logger reintroduced
Browse files Browse the repository at this point in the history
mkyc committed Nov 26, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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.