Fix STDOUT and STDERR direction based on Log.* calls

Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
Nathan LeClaire 2015-12-15 18:01:42 -08:00
parent fb916f2389
commit 06c97cfb17
12 changed files with 136 additions and 292 deletions

View File

@ -88,7 +88,6 @@ func main() {
return return
} }
log.Logger = log.NewLogrusMachineLogger()
setDebugOutputLevel() setDebugOutputLevel()
cli.AppHelpTemplate = AppHelpTemplate cli.AppHelpTemplate = AppHelpTemplate
cli.CommandHelpTemplate = CommandHelpTemplate cli.CommandHelpTemplate = CommandHelpTemplate

View File

@ -2,6 +2,7 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/check" "github.com/docker/machine/libmachine/check"
@ -11,7 +12,7 @@ import (
func cmdConfig(c CommandLine, api libmachine.API) error { func cmdConfig(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is // Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell) // being run (it is intended to be run in a subshell)
log.RedirectStdOutToStdErr() log.SetOut(os.Stderr)
if len(c.Args()) != 1 { if len(c.Args()) != 1 {
return ErrExpectedOneMachine return ErrExpectedOneMachine

View File

@ -50,7 +50,7 @@ func cmdEnv(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is // Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell) // being run (it is intended to be run in a subshell)
log.RedirectStdOutToStdErr() log.SetOut(os.Stderr)
if c.Bool("unset") { if c.Bool("unset") {
shellCfg, err = shellCfgUnset(c, api) shellCfg, err = shellCfgUnset(c, api)

View File

@ -81,11 +81,13 @@ func TestExecServer(t *testing.T) {
logReader, logWriter := io.Pipe() logReader, logWriter := io.Pipe()
log.SetDebug(true) log.SetDebug(true)
log.SetOutput(logWriter) log.SetOut(logWriter)
log.SetErr(logWriter)
defer func() { defer func() {
log.SetDebug(false) log.SetDebug(false)
log.SetOutput(os.Stderr) log.SetOut(os.Stdout)
log.SetErr(os.Stderr)
}() }()
stdoutReader, stdoutWriter := io.Pipe() stdoutReader, stdoutWriter := io.Pipe()

View File

@ -4,12 +4,15 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"sync"
) )
type FmtMachineLogger struct { type FmtMachineLogger struct {
out io.Writer out io.Writer
err io.Writer err io.Writer
debug bool debug bool
historyLock *sync.Mutex
history []string
} }
// NewFmtMachineLogger creates a MachineLogger implementation used by the drivers // NewFmtMachineLogger creates a MachineLogger implementation used by the drivers
@ -18,66 +21,91 @@ func NewFmtMachineLogger() MachineLogger {
out: os.Stdout, out: os.Stdout,
err: os.Stderr, err: os.Stderr,
debug: false, debug: false,
historyLock: &sync.Mutex{},
history: []string{},
} }
} }
func (ml *FmtMachineLogger) RedirectStdOutToStdErr() {
ml.out = ml.err
}
func (ml *FmtMachineLogger) SetDebug(debug bool) { func (ml *FmtMachineLogger) SetDebug(debug bool) {
ml.debug = debug ml.debug = debug
} }
func (ml *FmtMachineLogger) SetOutput(out io.Writer) { func (ml *FmtMachineLogger) SetOut(out io.Writer) {
ml.out = out ml.out = out
ml.err = out }
func (ml *FmtMachineLogger) SetErr(err io.Writer) {
ml.err = err
} }
func (ml *FmtMachineLogger) Debug(args ...interface{}) { func (ml *FmtMachineLogger) Debug(args ...interface{}) {
ml.record(args...)
if ml.debug { if ml.debug {
fmt.Fprintln(ml.err, args...) fmt.Fprintln(ml.err, args...)
} }
} }
func (ml *FmtMachineLogger) Debugf(fmtString string, args ...interface{}) { func (ml *FmtMachineLogger) Debugf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
if ml.debug { if ml.debug {
fmt.Fprintf(ml.err, fmtString+"\n", args...) fmt.Fprintf(ml.err, fmtString+"\n", args...)
} }
} }
func (ml *FmtMachineLogger) Error(args ...interface{}) { func (ml *FmtMachineLogger) Error(args ...interface{}) {
fmt.Fprintln(ml.out, args...) ml.record(args...)
fmt.Fprintln(ml.err, args...)
} }
func (ml *FmtMachineLogger) Errorf(fmtString string, args ...interface{}) { func (ml *FmtMachineLogger) Errorf(fmtString string, args ...interface{}) {
fmt.Fprintf(ml.out, fmtString+"\n", args...) ml.recordf(fmtString, args...)
fmt.Fprintf(ml.err, fmtString+"\n", args...)
} }
func (ml *FmtMachineLogger) Info(args ...interface{}) { func (ml *FmtMachineLogger) Info(args ...interface{}) {
ml.record(args...)
fmt.Fprintln(ml.out, args...) fmt.Fprintln(ml.out, args...)
} }
func (ml *FmtMachineLogger) Infof(fmtString string, args ...interface{}) { func (ml *FmtMachineLogger) Infof(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
fmt.Fprintf(ml.out, fmtString+"\n", args...) fmt.Fprintf(ml.out, fmtString+"\n", args...)
} }
func (ml *FmtMachineLogger) Fatal(args ...interface{}) { func (ml *FmtMachineLogger) Fatal(args ...interface{}) {
fmt.Fprintln(ml.out, args...) ml.record(args...)
fmt.Fprintln(ml.err, args...)
os.Exit(1)
} }
func (ml *FmtMachineLogger) Fatalf(fmtString string, args ...interface{}) { func (ml *FmtMachineLogger) Fatalf(fmtString string, args ...interface{}) {
fmt.Fprintf(ml.out, fmtString+"\n", args...) ml.recordf(fmtString, args...)
fmt.Fprintf(ml.err, fmtString+"\n", args...)
os.Exit(1)
} }
func (ml *FmtMachineLogger) Warn(args ...interface{}) { func (ml *FmtMachineLogger) Warn(args ...interface{}) {
ml.record(args...)
fmt.Fprintln(ml.out, args...) fmt.Fprintln(ml.out, args...)
} }
func (ml *FmtMachineLogger) Warnf(fmtString string, args ...interface{}) { func (ml *FmtMachineLogger) Warnf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
fmt.Fprintf(ml.out, fmtString+"\n", args...) fmt.Fprintf(ml.out, fmtString+"\n", args...)
} }
func (ml *FmtMachineLogger) History() []string { func (ml *FmtMachineLogger) History() []string {
return []string{} return ml.history
}
func (ml *FmtMachineLogger) record(args ...interface{}) {
ml.historyLock.Lock()
defer ml.historyLock.Unlock()
ml.history = append(ml.history, fmt.Sprint(args...))
}
func (ml *FmtMachineLogger) recordf(fmtString string, args ...interface{}) {
ml.historyLock.Lock()
defer ml.historyLock.Unlock()
ml.history = append(ml.history, fmt.Sprintf(fmtString, args...))
} }

View File

@ -1,21 +1,67 @@
package log package log
import ( import (
"bufio"
"io"
"io/ioutil"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestFmtDebug(t *testing.T) { func captureOutput(testLogger MachineLogger, lambda func()) string {
pipeReader, pipeWriter := io.Pipe()
scanner := bufio.NewScanner(pipeReader)
testLogger.SetOut(pipeWriter)
go lambda()
scanner.Scan()
return scanner.Text()
}
func captureError(testLogger MachineLogger, lambda func()) string {
pipeReader, pipeWriter := io.Pipe()
scanner := bufio.NewScanner(pipeReader)
testLogger.SetErr(pipeWriter)
go lambda()
scanner.Scan()
return scanner.Text()
}
func TestSetDebugToTrue(t *testing.T) {
testLogger := NewFmtMachineLogger().(*FmtMachineLogger)
testLogger.SetDebug(true)
assert.Equal(t, true, testLogger.debug)
}
func TestSetDebugToFalse(t *testing.T) {
testLogger := NewFmtMachineLogger().(*FmtMachineLogger)
testLogger.SetDebug(true)
testLogger.SetDebug(false)
assert.Equal(t, false, testLogger.debug)
}
func TestSetOut(t *testing.T) {
testLogger := NewFmtMachineLogger().(*FmtMachineLogger)
testLogger.SetOut(ioutil.Discard)
assert.Equal(t, ioutil.Discard, testLogger.out)
}
func TestSetErr(t *testing.T) {
testLogger := NewFmtMachineLogger().(*FmtMachineLogger)
testLogger.SetErr(ioutil.Discard)
assert.Equal(t, ioutil.Discard, testLogger.err)
}
func TestDebug(t *testing.T) {
testLogger := NewFmtMachineLogger() testLogger := NewFmtMachineLogger()
testLogger.SetDebug(true) testLogger.SetDebug(true)
result := captureOutput(testLogger, func() { testLogger.Debug("debug") }) result := captureError(testLogger, func() { testLogger.Debug("debug") })
assert.Equal(t, result, "debug") assert.Equal(t, result, "debug")
} }
func TestFmtInfo(t *testing.T) { func TestInfo(t *testing.T) {
testLogger := NewFmtMachineLogger() testLogger := NewFmtMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Info("info") }) result := captureOutput(testLogger, func() { testLogger.Info("info") })
@ -23,7 +69,7 @@ func TestFmtInfo(t *testing.T) {
assert.Equal(t, result, "info") assert.Equal(t, result, "info")
} }
func TestFmtWarn(t *testing.T) { func TestWarn(t *testing.T) {
testLogger := NewFmtMachineLogger() testLogger := NewFmtMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Warn("warn") }) result := captureOutput(testLogger, func() { testLogger.Warn("warn") })
@ -31,10 +77,21 @@ func TestFmtWarn(t *testing.T) {
assert.Equal(t, result, "warn") assert.Equal(t, result, "warn")
} }
func TestFmtError(t *testing.T) { func TestError(t *testing.T) {
testLogger := NewFmtMachineLogger() testLogger := NewFmtMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Error("error") }) result := captureError(testLogger, func() { testLogger.Error("error") })
assert.Equal(t, result, "error") assert.Equal(t, result, "error")
} }
func TestEntriesAreCollected(t *testing.T) {
testLogger := NewFmtMachineLogger()
testLogger.Debug("debug")
testLogger.Info("info")
testLogger.Error("error")
assert.Equal(t, 3, len(testLogger.History()))
assert.Equal(t, "debug", testLogger.History()[0])
assert.Equal(t, "info", testLogger.History()[1])
assert.Equal(t, "error", testLogger.History()[2])
}

View File

@ -8,17 +8,13 @@ import (
const redactedText = "<REDACTED>" const redactedText = "<REDACTED>"
var ( var (
Logger MachineLogger logger = NewFmtMachineLogger()
// (?s) enables '.' to match '\n' -- see https://golang.org/pkg/regexp/syntax/ // (?s) enables '.' to match '\n' -- see https://golang.org/pkg/regexp/syntax/
certRegex = regexp.MustCompile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----") certRegex = regexp.MustCompile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----")
keyRegex = regexp.MustCompile("(?s)-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----") keyRegex = regexp.MustCompile("(?s)-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----")
) )
func init() {
Logger = NewFmtMachineLogger()
}
func stripSecrets(original []string) []string { func stripSecrets(original []string) []string {
stripped := []string{} stripped := []string{}
for _, line := range original { for _, line := range original {
@ -29,59 +25,58 @@ func stripSecrets(original []string) []string {
return stripped return stripped
} }
// RedirectStdOutToStdErr prevents any log from corrupting the output
func RedirectStdOutToStdErr() {
Logger.RedirectStdOutToStdErr()
}
func Debug(args ...interface{}) { func Debug(args ...interface{}) {
Logger.Debug(args...) logger.Debug(args...)
} }
func Debugf(fmtString string, args ...interface{}) { func Debugf(fmtString string, args ...interface{}) {
Logger.Debugf(fmtString, args...) logger.Debugf(fmtString, args...)
} }
func Error(args ...interface{}) { func Error(args ...interface{}) {
Logger.Error(args...) logger.Error(args...)
} }
func Errorf(fmtString string, args ...interface{}) { func Errorf(fmtString string, args ...interface{}) {
Logger.Errorf(fmtString, args...) logger.Errorf(fmtString, args...)
} }
func Info(args ...interface{}) { func Info(args ...interface{}) {
Logger.Info(args...) logger.Info(args...)
} }
func Infof(fmtString string, args ...interface{}) { func Infof(fmtString string, args ...interface{}) {
Logger.Infof(fmtString, args...) logger.Infof(fmtString, args...)
} }
func Fatal(args ...interface{}) { func Fatal(args ...interface{}) {
Logger.Fatal(args...) logger.Fatal(args...)
} }
func Fatalf(fmtString string, args ...interface{}) { func Fatalf(fmtString string, args ...interface{}) {
Logger.Fatalf(fmtString, args...) logger.Fatalf(fmtString, args...)
} }
func Warn(args ...interface{}) { func Warn(args ...interface{}) {
Logger.Warn(args...) logger.Warn(args...)
} }
func Warnf(fmtString string, args ...interface{}) { func Warnf(fmtString string, args ...interface{}) {
Logger.Warnf(fmtString, args...) logger.Warnf(fmtString, args...)
} }
func SetDebug(debug bool) { func SetDebug(debug bool) {
Logger.SetDebug(debug) logger.SetDebug(debug)
} }
func SetOutput(out io.Writer) { func SetOut(out io.Writer) {
Logger.SetOutput(out) logger.SetOut(out)
}
func SetErr(err io.Writer) {
logger.SetErr(err)
} }
func History() []string { func History() []string {
return stripSecrets(Logger.History()) return stripSecrets(logger.History())
} }

View File

@ -1,112 +0,0 @@
package log
import (
"io"
"fmt"
"sync"
"github.com/Sirupsen/logrus"
)
type LogrusMachineLogger struct {
history []string
historyLock sync.Locker
logger *logrus.Logger
}
// NewLogrusMachineLogger creates the MachineLogger implementation used by the docker-machine
func NewLogrusMachineLogger() MachineLogger {
logrusLogger := logrus.New()
logrusLogger.Level = logrus.InfoLevel
logrusLogger.Formatter = new(MachineFormatter)
return &LogrusMachineLogger{[]string{}, &sync.Mutex{}, logrusLogger}
}
// RedirectStdOutToStdErr prevents any log from corrupting the output
func (ml *LogrusMachineLogger) RedirectStdOutToStdErr() {
ml.logger.Level = logrus.ErrorLevel
}
func (ml *LogrusMachineLogger) SetDebug(debug bool) {
if debug {
ml.logger.Level = logrus.DebugLevel
} else {
ml.logger.Level = logrus.InfoLevel
}
}
func (ml *LogrusMachineLogger) SetOutput(out io.Writer) {
ml.logger.Out = out
}
func (ml *LogrusMachineLogger) Logger() *logrus.Logger {
return ml.logger
}
func (ml *LogrusMachineLogger) Debug(args ...interface{}) {
ml.record(args...)
ml.logger.Debug(args...)
}
func (ml *LogrusMachineLogger) Debugf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
ml.logger.Debugf(fmtString, args...)
}
func (ml *LogrusMachineLogger) Error(args ...interface{}) {
ml.record(args...)
ml.logger.Error(args...)
}
func (ml *LogrusMachineLogger) Errorf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
ml.logger.Errorf(fmtString, args...)
}
func (ml *LogrusMachineLogger) Info(args ...interface{}) {
ml.record(args...)
ml.logger.Info(args...)
}
func (ml *LogrusMachineLogger) Infof(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
ml.logger.Infof(fmtString, args...)
}
func (ml *LogrusMachineLogger) Fatal(args ...interface{}) {
ml.record(args...)
ml.logger.Fatal(args...)
}
func (ml *LogrusMachineLogger) Fatalf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
ml.logger.Fatalf(fmtString, args...)
}
func (ml *LogrusMachineLogger) Warn(args ...interface{}) {
ml.record(args...)
ml.logger.Warn(args...)
}
func (ml *LogrusMachineLogger) Warnf(fmtString string, args ...interface{}) {
ml.recordf(fmtString, args...)
ml.logger.Warnf(fmtString, args...)
}
func (ml *LogrusMachineLogger) History() []string {
return ml.history
}
func (ml *LogrusMachineLogger) record(args ...interface{}) {
ml.historyLock.Lock()
defer ml.historyLock.Unlock()
ml.history = append(ml.history, fmt.Sprint(args...))
}
func (ml *LogrusMachineLogger) recordf(fmtString string, args ...interface{}) {
ml.historyLock.Lock()
defer ml.historyLock.Unlock()
ml.history = append(ml.history, fmt.Sprintf(fmtString, args...))
}

View File

@ -1,89 +0,0 @@
package log
import (
"testing"
"bufio"
"io"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestDefaultLevelIsInfo(t *testing.T) {
testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger)
assert.Equal(t, testLogger.Logger().Level, logrus.InfoLevel)
}
func TestSetDebugToTrue(t *testing.T) {
testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger)
testLogger.SetDebug(true)
assert.Equal(t, testLogger.Logger().Level, logrus.DebugLevel)
}
func TestSetDebugToFalse(t *testing.T) {
testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger)
testLogger.SetDebug(true)
testLogger.SetDebug(false)
assert.Equal(t, testLogger.Logger().Level, logrus.InfoLevel)
}
func TestSetSilenceOutput(t *testing.T) {
testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger)
testLogger.RedirectStdOutToStdErr()
assert.Equal(t, testLogger.Logger().Level, logrus.ErrorLevel)
}
func TestDebugOutput(t *testing.T) {
testLogger := NewLogrusMachineLogger()
testLogger.SetDebug(true)
result := captureOutput(testLogger, func() { testLogger.Debug("debug") })
assert.Equal(t, result, "debug")
}
func TestInfoOutput(t *testing.T) {
testLogger := NewLogrusMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Info("info") })
assert.Equal(t, result, "info")
}
func TestWarnOutput(t *testing.T) {
testLogger := NewLogrusMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Warn("warn") })
assert.Equal(t, result, "warn")
}
func TestErrorOutput(t *testing.T) {
testLogger := NewLogrusMachineLogger()
result := captureOutput(testLogger, func() { testLogger.Error("error") })
assert.Equal(t, result, "error")
}
func TestEntriesAreCollected(t *testing.T) {
testLogger := NewLogrusMachineLogger()
testLogger.RedirectStdOutToStdErr()
testLogger.Debug("debug")
testLogger.Info("info")
testLogger.Error("error")
assert.Equal(t, 3, len(testLogger.History()))
assert.Equal(t, "debug", testLogger.History()[0])
assert.Equal(t, "info", testLogger.History()[1])
assert.Equal(t, "error", testLogger.History()[2])
}
func captureOutput(testLogger MachineLogger, lambda func()) string {
pipeReader, pipeWriter := io.Pipe()
scanner := bufio.NewScanner(pipeReader)
testLogger.SetOutput(pipeWriter)
go lambda()
scanner.Scan()
return scanner.Text()
}

View File

@ -1,19 +0,0 @@
package log
import (
"bytes"
"github.com/Sirupsen/logrus"
)
type MachineFormatter struct {
}
func (d *MachineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b := &bytes.Buffer{}
b.WriteString(entry.Message)
b.WriteByte('\n')
return b.Bytes(), nil
}

View File

@ -1,17 +0,0 @@
package log
import (
"testing"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestWrite(t *testing.T) {
entry := logrus.NewEntry(logrus.New())
entry.Message = "foobar"
formatter := MachineFormatter{}
bytes, err := formatter.Format(entry)
assert.Nil(t, err)
assert.Equal(t, string(bytes[:]), "foobar\n")
}

View File

@ -3,11 +3,10 @@ package log
import "io" import "io"
type MachineLogger interface { type MachineLogger interface {
RedirectStdOutToStdErr()
SetDebug(debug bool) SetDebug(debug bool)
SetOutput(io.Writer) SetOut(io.Writer)
SetErr(io.Writer)
Debug(args ...interface{}) Debug(args ...interface{})
Debugf(fmtString string, args ...interface{}) Debugf(fmtString string, args ...interface{})