mirror of https://github.com/docker/docs.git
				
				
				
			bugsnag integration
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
This commit is contained in:
		
							parent
							
								
									2051e6eeae
								
							
						
					
					
						commit
						4f0c24483b
					
				|  | @ -88,6 +88,7 @@ func main() { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	log.Logger = log.NewLogrusMachineLogger() | ||||
| 	setDebugOutputLevel() | ||||
| 	cli.AppHelpTemplate = AppHelpTemplate | ||||
| 	cli.CommandHelpTemplate = CommandHelpTemplate | ||||
|  | @ -149,6 +150,12 @@ func main() { | |||
| 			Name:   "native-ssh", | ||||
| 			Usage:  "Use the native (Go-based) SSH implementation.", | ||||
| 		}, | ||||
| 		cli.StringFlag{ | ||||
| 			EnvVar: "MACHINE_BUGSNAG_API_TOKEN", | ||||
| 			Name:   "bugsnag-api-token", | ||||
| 			Usage:  "BugSnag API token for crash reporting", | ||||
| 			Value:  "", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if err := app.Run(os.Args); err != nil { | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import ( | |||
| 	"github.com/docker/machine/commands/mcndirs" | ||||
| 	"github.com/docker/machine/libmachine" | ||||
| 	"github.com/docker/machine/libmachine/cert" | ||||
| 	"github.com/docker/machine/libmachine/crashreport" | ||||
| 	"github.com/docker/machine/libmachine/host" | ||||
| 	"github.com/docker/machine/libmachine/log" | ||||
| 	"github.com/docker/machine/libmachine/mcnutils" | ||||
|  | @ -102,6 +103,8 @@ func fatalOnError(command func(commandLine CommandLine, api libmachine.API) erro | |||
| 		api.GithubAPIToken = context.GlobalString("github-api-token") | ||||
| 		api.Filestore.Path = context.GlobalString("storage-path") | ||||
| 
 | ||||
| 		crashreport.Configure(context.GlobalString("bugsnag-api-token")) | ||||
| 
 | ||||
| 		// TODO (nathanleclaire): These should ultimately be accessed
 | ||||
| 		// through the libmachine client by the rest of the code and
 | ||||
| 		// not through their respective modules.  For now, however,
 | ||||
|  |  | |||
|  | @ -139,3 +139,21 @@ Make sure to specify the machine name as an argument: | |||
| 
 | ||||
|     $ docker-machine stop dev | ||||
|     $ docker-machine start dev | ||||
| 
 | ||||
| # Crash Reporting | ||||
| 
 | ||||
| Provisioning a host is a complex matter that can fail for a lot of reasons. | ||||
| Some of those reasons lies in your very workstation that can have a wide | ||||
| variety of shell, network configuration, vpn, proxy and firewalls or from reasons | ||||
| on the other end of the chain: your cloud provider or the network in between. | ||||
| 
 | ||||
| To help `docker-machine` be as stable as possible, we added a monitoring of crashes | ||||
| whenever you try to `create` or `upgrade` a host. This will send over https to bugsnag | ||||
| a couple of information : your docker-machine version, build, OS, ARCH, the path to your | ||||
|  current shell and the history of the last command as you could see it with a `-D` option. | ||||
| Those data are only there to help us pinpoint recurring issue with docker-machine and will only | ||||
| be transmitted in the case of a crash of docker-machine. | ||||
| 
 | ||||
| If you're worried about thatm you can create a `no-error-report` in the `$HOME/.docker/machine` | ||||
| directory, and we will not gather nor send any data. | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,111 @@ | |||
| package crashreport | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 
 | ||||
| 	"bytes" | ||||
| 
 | ||||
| 	"os/exec" | ||||
| 
 | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"errors" | ||||
| 
 | ||||
| 	"github.com/bugsnag/bugsnag-go" | ||||
| 	"github.com/docker/machine/commands/mcndirs" | ||||
| 	"github.com/docker/machine/libmachine/log" | ||||
| 	"github.com/docker/machine/version" | ||||
| ) | ||||
| 
 | ||||
| // We bundle a bugnsag key, but we disable its usage for now
 | ||||
| // this ease testing just set useDefaultKey to true
 | ||||
| // and yet we bundle the code without activating it by default.
 | ||||
| var useDefaultKey = false | ||||
| 
 | ||||
| const defaultAPIKey = "a9697f9a010c33ee218a65e5b1f3b0c1" | ||||
| 
 | ||||
| var apiKey string | ||||
| 
 | ||||
| // Configure the apikey for bugnag
 | ||||
| func Configure(key string) { | ||||
| 	if key != "" { | ||||
| 		apiKey = key | ||||
| 		return | ||||
| 	} | ||||
| 	if useDefaultKey { | ||||
| 		apiKey = defaultAPIKey | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Send through http the crash report to bugsnag need a call to Configure(apiKey) before
 | ||||
| func Send(error error) error { | ||||
| 	if noReportFileExist() { | ||||
| 		err := errors.New("Not sending report since the optout file exist.") | ||||
| 		log.Debug(err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if apiKey == "" { | ||||
| 		err := errors.New("Not sending report since no api key has been set.") | ||||
| 		log.Debug(err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	bugsnag.Configure(bugsnag.Configuration{ | ||||
| 		APIKey: apiKey, | ||||
| 		// XXX we need to abuse bugsnag metrics to get the OS/ARCH information as a usable filter
 | ||||
| 		// Can do that with either "stage" or "hostname"
 | ||||
| 		ReleaseStage:    fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH), | ||||
| 		ProjectPackages: []string{"github.com/docker/machine/[^v]*"}, | ||||
| 		AppVersion:      version.FullVersion(), | ||||
| 		Synchronous:     true, | ||||
| 		PanicHandler:    func() {}, | ||||
| 		Logger:          new(logger), | ||||
| 	}) | ||||
| 
 | ||||
| 	metaData := bugsnag.MetaData{} | ||||
| 
 | ||||
| 	metaData.Add("app", "compiler", fmt.Sprintf("%s (%s)", runtime.Compiler, runtime.Version())) | ||||
| 	metaData.Add("device", "os", runtime.GOOS) | ||||
| 	metaData.Add("device", "arch", runtime.GOARCH) | ||||
| 
 | ||||
| 	detectRunningShell(&metaData) | ||||
| 	detectUname(&metaData) | ||||
| 
 | ||||
| 	var buffer bytes.Buffer | ||||
| 	for _, message := range log.History() { | ||||
| 		buffer.WriteString(message + "\n") | ||||
| 	} | ||||
| 	metaData.Add("history", "trace", buffer.String()) | ||||
| 	return bugsnag.Notify(error, metaData) | ||||
| } | ||||
| 
 | ||||
| func noReportFileExist() bool { | ||||
| 	optOutFilePath := filepath.Join(mcndirs.GetBaseDir(), "no-error-report") | ||||
| 	if _, err := os.Stat(optOutFilePath); os.IsNotExist(err) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func detectRunningShell(metaData *bugsnag.MetaData) { | ||||
| 	shell := os.Getenv("SHELL") | ||||
| 	if shell != "" { | ||||
| 		metaData.Add("device", "shell", shell) | ||||
| 	} | ||||
| 	shell = os.Getenv("__fish_bin_dir") | ||||
| 	if shell != "" { | ||||
| 		metaData.Add("device", "shell", shell) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func detectUname(metaData *bugsnag.MetaData) { | ||||
| 	cmd := exec.Command("uname", "-s") | ||||
| 	output, err := cmd.Output() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	metaData.Add("device", "uname", string(output)) | ||||
| } | ||||
|  | @ -0,0 +1,9 @@ | |||
| package crashreport | ||||
| 
 | ||||
| import "github.com/docker/machine/libmachine/log" | ||||
| 
 | ||||
| type logger struct{} | ||||
| 
 | ||||
| func (d *logger) Printf(fmtString string, args ...interface{}) { | ||||
| 	log.Debugf(fmtString, args) | ||||
| } | ||||
|  | @ -23,8 +23,8 @@ var ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	pluginOutPrefix     = "(%s) " | ||||
| 	pluginErrPrefix     = "(%s) DBG | " | ||||
| 	pluginOut           = "(%s) %s" | ||||
| 	pluginErr           = "(%s) DBG | %s" | ||||
| 	PluginEnvKey        = "MACHINE_PLUGIN_TOKEN" | ||||
| 	PluginEnvVal        = "42" | ||||
| 	PluginEnvDriverName = "MACHINE_PLUGIN_DRIVER_NAME" | ||||
|  | @ -210,9 +210,9 @@ func (lbp *Plugin) execServer() error { | |||
| 	for { | ||||
| 		select { | ||||
| 		case out := <-stdOutCh: | ||||
| 			log.Info(fmt.Sprintf(pluginOutPrefix, lbp.MachineName), out) | ||||
| 			log.Infof(pluginOut, lbp.MachineName, out) | ||||
| 		case err := <-stdErrCh: | ||||
| 			log.Debug(fmt.Sprintf(pluginErrPrefix, lbp.MachineName), err) | ||||
| 			log.Debugf(pluginErr, lbp.MachineName, err) | ||||
| 		case _ = <-lbp.stopCh: | ||||
| 			stopStdoutCh <- true | ||||
| 			stopStderrCh <- true | ||||
|  |  | |||
|  | @ -78,11 +78,9 @@ func TestLocalBinaryPluginClose(t *testing.T) { | |||
| } | ||||
| 
 | ||||
| func TestExecServer(t *testing.T) { | ||||
| 	log.SetDebug(true) | ||||
| 	machineName := "test" | ||||
| 
 | ||||
| 	logReader, logWriter := io.Pipe() | ||||
| 
 | ||||
| 	log.SetDebug(true) | ||||
| 	log.SetOutput(logWriter) | ||||
| 
 | ||||
| 	defer func() { | ||||
|  | @ -98,6 +96,7 @@ func TestExecServer(t *testing.T) { | |||
| 		stderr: stderrReader, | ||||
| 	} | ||||
| 
 | ||||
| 	machineName := "test" | ||||
| 	lbp := &Plugin{ | ||||
| 		MachineName: machineName, | ||||
| 		Executor:    fe, | ||||
|  | @ -112,12 +111,10 @@ func TestExecServer(t *testing.T) { | |||
| 		finalErr <- lbp.execServer() | ||||
| 	}() | ||||
| 
 | ||||
| 	expectedAddr := "127.0.0.1:12345" | ||||
| 	expectedPluginOut := "Doing some fun plugin stuff..." | ||||
| 	expectedPluginErr := "Uh oh, something in plugin went wrong..." | ||||
| 
 | ||||
| 	logScanner := bufio.NewScanner(logReader) | ||||
| 
 | ||||
| 	// Write the ip address
 | ||||
| 	expectedAddr := "127.0.0.1:12345" | ||||
| 	if _, err := io.WriteString(stdoutWriter, expectedAddr+"\n"); err != nil { | ||||
| 		t.Fatalf("Error attempting to write plugin address: %s", err) | ||||
| 	} | ||||
|  | @ -126,22 +123,24 @@ func TestExecServer(t *testing.T) { | |||
| 		t.Fatalf("Expected to read the expected address properly in server but did not") | ||||
| 	} | ||||
| 
 | ||||
| 	expectedOut := fmt.Sprintf("%s%s", fmt.Sprintf(pluginOutPrefix, machineName), expectedPluginOut) | ||||
| 
 | ||||
| 	// Write a log in stdout
 | ||||
| 	expectedPluginOut := "Doing some fun plugin stuff..." | ||||
| 	if _, err := io.WriteString(stdoutWriter, expectedPluginOut+"\n"); err != nil { | ||||
| 		t.Fatalf("Error attempting to write to out in plugin: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	expectedOut := fmt.Sprintf(pluginOut, machineName, expectedPluginOut) | ||||
| 	if logScanner.Scan(); logScanner.Text() != expectedOut { | ||||
| 		t.Fatalf("Output written to log was not what we expected\nexpected: %s\nactual:   %s", expectedOut, logScanner.Text()) | ||||
| 	} | ||||
| 
 | ||||
| 	expectedErr := fmt.Sprintf("%s%s", fmt.Sprintf(pluginErrPrefix, machineName), expectedPluginErr) | ||||
| 
 | ||||
| 	// Write a log in stderr
 | ||||
| 	expectedPluginErr := "Uh oh, something in plugin went wrong..." | ||||
| 	if _, err := io.WriteString(stderrWriter, expectedPluginErr+"\n"); err != nil { | ||||
| 		t.Fatalf("Error attempting to write to err in plugin: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	expectedErr := fmt.Sprintf(pluginErr, machineName, expectedPluginErr) | ||||
| 	if logScanner.Scan(); logScanner.Text() != expectedErr { | ||||
| 		t.Fatalf("Error written to log was not what we expected\nexpected: %s\nactual:   %s", expectedErr, logScanner.Text()) | ||||
| 	} | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/machine/libmachine/auth" | ||||
| 	"github.com/docker/machine/libmachine/crashreport" | ||||
| 	"github.com/docker/machine/libmachine/drivers" | ||||
| 	"github.com/docker/machine/libmachine/engine" | ||||
| 	"github.com/docker/machine/libmachine/log" | ||||
|  | @ -134,11 +135,13 @@ func (h *Host) Upgrade() error { | |||
| 
 | ||||
| 	provisioner, err := provision.DetectProvisioner(h.Driver) | ||||
| 	if err != nil { | ||||
| 		crashreport.Send(err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	log.Info("Upgrading docker...") | ||||
| 	if err := provisioner.Package("docker", pkgaction.Upgrade); err != nil { | ||||
| 		crashreport.Send(err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"github.com/docker/machine/libmachine/auth" | ||||
| 	"github.com/docker/machine/libmachine/cert" | ||||
| 	"github.com/docker/machine/libmachine/check" | ||||
| 	"github.com/docker/machine/libmachine/crashreport" | ||||
| 	"github.com/docker/machine/libmachine/drivers" | ||||
| 	"github.com/docker/machine/libmachine/engine" | ||||
| 	"github.com/docker/machine/libmachine/host" | ||||
|  | @ -96,6 +97,18 @@ func (api *Client) Create(h *host.Host) error { | |||
| 
 | ||||
| 	log.Info("Creating machine...") | ||||
| 
 | ||||
| 	if err := api.performCreate(h); err != nil { | ||||
| 		crashreport.Send(err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	log.Debug("Reticulating splines...") | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (api *Client) performCreate(h *host.Host) error { | ||||
| 
 | ||||
| 	if err := h.Driver.Create(); err != nil { | ||||
| 		return fmt.Errorf("Error in driver during machine creation: %s", err) | ||||
| 	} | ||||
|  | @ -136,7 +149,6 @@ func (api *Client) Create(h *host.Host) error { | |||
| 		log.Info("Docker is up and running!") | ||||
| 	} | ||||
| 
 | ||||
| 	log.Debug("Reticulating splines...") | ||||
| 
 | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,83 @@ | |||
| package log | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
| 
 | ||||
| type FmtMachineLogger struct { | ||||
| 	out   io.Writer | ||||
| 	err   io.Writer | ||||
| 	debug bool | ||||
| } | ||||
| 
 | ||||
| // NewFmtMachineLogger creates a MachineLogger implementation used by the drivers
 | ||||
| func NewFmtMachineLogger() MachineLogger { | ||||
| 	return &FmtMachineLogger{ | ||||
| 		out:   os.Stdout, | ||||
| 		err:   os.Stderr, | ||||
| 		debug: false, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) RedirectStdOutToStdErr() { | ||||
| 	ml.out = ml.err | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) SetDebug(debug bool) { | ||||
| 	ml.debug = debug | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) SetOutput(out io.Writer) { | ||||
| 	ml.out = out | ||||
| 	ml.err = out | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Debug(args ...interface{}) { | ||||
| 	if ml.debug { | ||||
| 		fmt.Fprintln(ml.err, args...) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Debugf(fmtString string, args ...interface{}) { | ||||
| 	if ml.debug { | ||||
| 		fmt.Fprintf(ml.err, fmtString+"\n", args...) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Error(args ...interface{}) { | ||||
| 	fmt.Fprintln(ml.out, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Errorf(fmtString string, args ...interface{}) { | ||||
| 	fmt.Fprintf(ml.out, fmtString+"\n", args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Info(args ...interface{}) { | ||||
| 	fmt.Fprintln(ml.out, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Infof(fmtString string, args ...interface{}) { | ||||
| 	fmt.Fprintf(ml.out, fmtString+"\n", args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Fatal(args ...interface{}) { | ||||
| 	fmt.Fprintln(ml.out, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Fatalf(fmtString string, args ...interface{}) { | ||||
| 	fmt.Fprintf(ml.out, fmtString+"\n", args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Warn(args ...interface{}) { | ||||
| 	fmt.Fprintln(ml.out, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) Warnf(fmtString string, args ...interface{}) { | ||||
| 	fmt.Fprintf(ml.out, fmtString+"\n", args...) | ||||
| } | ||||
| 
 | ||||
| func (ml *FmtMachineLogger) History() []string { | ||||
| 	return []string{} | ||||
| } | ||||
|  | @ -0,0 +1,40 @@ | |||
| package log | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func TestFmtDebug(t *testing.T) { | ||||
| 	testLogger := NewFmtMachineLogger() | ||||
| 	testLogger.SetDebug(true) | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Debug("debug") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "debug") | ||||
| } | ||||
| 
 | ||||
| func TestFmtInfo(t *testing.T) { | ||||
| 	testLogger := NewFmtMachineLogger() | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Info("info") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "info") | ||||
| } | ||||
| 
 | ||||
| func TestFmtWarn(t *testing.T) { | ||||
| 	testLogger := NewFmtMachineLogger() | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Warn("warn") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "warn") | ||||
| } | ||||
| 
 | ||||
| func TestFmtError(t *testing.T) { | ||||
| 	testLogger := NewFmtMachineLogger() | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Error("error") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "error") | ||||
| } | ||||
|  | @ -2,65 +2,65 @@ package log | |||
| 
 | ||||
| import "io" | ||||
| 
 | ||||
| var logger MachineLogger | ||||
| var Logger MachineLogger | ||||
| 
 | ||||
| func init() { | ||||
| 	logger = NewMachineLogger() | ||||
| 	Logger = NewFmtMachineLogger() | ||||
| } | ||||
| 
 | ||||
| // RedirectStdOutToStdErr prevents any log from corrupting the output
 | ||||
| func RedirectStdOutToStdErr() { | ||||
| 	logger.RedirectStdOutToStdErr() | ||||
| 	Logger.RedirectStdOutToStdErr() | ||||
| } | ||||
| 
 | ||||
| func Debug(args ...interface{}) { | ||||
| 	logger.Debug(args...) | ||||
| 	Logger.Debug(args...) | ||||
| } | ||||
| 
 | ||||
| func Debugf(fmtString string, args ...interface{}) { | ||||
| 	logger.Debugf(fmtString, args...) | ||||
| 	Logger.Debugf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func Error(args ...interface{}) { | ||||
| 	logger.Error(args...) | ||||
| 	Logger.Error(args...) | ||||
| } | ||||
| 
 | ||||
| func Errorf(fmtString string, args ...interface{}) { | ||||
| 	logger.Errorf(fmtString, args...) | ||||
| 	Logger.Errorf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func Info(args ...interface{}) { | ||||
| 	logger.Info(args...) | ||||
| 	Logger.Info(args...) | ||||
| } | ||||
| 
 | ||||
| func Infof(fmtString string, args ...interface{}) { | ||||
| 	logger.Infof(fmtString, args...) | ||||
| 	Logger.Infof(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func Fatal(args ...interface{}) { | ||||
| 	logger.Fatal(args...) | ||||
| 	Logger.Fatal(args...) | ||||
| } | ||||
| 
 | ||||
| func Fatalf(fmtString string, args ...interface{}) { | ||||
| 	logger.Fatalf(fmtString, args...) | ||||
| 	Logger.Fatalf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func Warn(args ...interface{}) { | ||||
| 	logger.Warn(args...) | ||||
| 	Logger.Warn(args...) | ||||
| } | ||||
| 
 | ||||
| func Warnf(fmtString string, args ...interface{}) { | ||||
| 	logger.Warnf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func Logger() interface{} { | ||||
| 	return logger | ||||
| 	Logger.Warnf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func SetDebug(debug bool) { | ||||
| 	logger.SetDebug(debug) | ||||
| 	Logger.SetDebug(debug) | ||||
| } | ||||
| 
 | ||||
| func SetOutput(out io.Writer) { | ||||
| 	logger.SetOutput(out) | ||||
| 	Logger.SetOutput(out) | ||||
| } | ||||
| 
 | ||||
| func History() []string { | ||||
| 	return Logger.History() | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| package log | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestFormatterIsSet(t *testing.T) { | ||||
| 
 | ||||
| } | ||||
|  | @ -3,26 +3,33 @@ package log | |||
| import ( | ||||
| 	"io" | ||||
| 
 | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| type LogrusMachineLogger struct { | ||||
| 	logger *logrus.Logger | ||||
| 	history     []string | ||||
| 	historyLock sync.Locker | ||||
| 	logger      *logrus.Logger | ||||
| } | ||||
| 
 | ||||
| func NewMachineLogger() MachineLogger { | ||||
| // 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{logrusLogger} | ||||
| 	return &LogrusMachineLogger{[]string{}, &sync.Mutex{}, logrusLogger} | ||||
| } | ||||
| 
 | ||||
| // RedirectStdOutToStdErr prevents any log from corrupting the output
 | ||||
| func (ml LogrusMachineLogger) RedirectStdOutToStdErr() { | ||||
| func (ml *LogrusMachineLogger) RedirectStdOutToStdErr() { | ||||
| 	ml.logger.Level = logrus.ErrorLevel | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) SetDebug(debug bool) { | ||||
| func (ml *LogrusMachineLogger) SetDebug(debug bool) { | ||||
| 	if debug { | ||||
| 		ml.logger.Level = logrus.DebugLevel | ||||
| 	} else { | ||||
|  | @ -30,50 +37,76 @@ func (ml LogrusMachineLogger) SetDebug(debug bool) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) SetOutput(out io.Writer) { | ||||
| func (ml *LogrusMachineLogger) SetOutput(out io.Writer) { | ||||
| 	ml.logger.Out = out | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Logger() interface{} { | ||||
| func (ml *LogrusMachineLogger) Logger() *logrus.Logger { | ||||
| 	return ml.logger | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Debug(args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Debug(args ...interface{}) { | ||||
| 	ml.record(args...) | ||||
| 	ml.logger.Debug(args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Debugf(fmtString string, args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Debugf(fmtString string, args ...interface{}) { | ||||
| 	ml.recordf(fmtString, args...) | ||||
| 	ml.logger.Debugf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Error(args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Error(args ...interface{}) { | ||||
| 	ml.record(args...) | ||||
| 	ml.logger.Error(args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Errorf(fmtString string, args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Errorf(fmtString string, args ...interface{}) { | ||||
| 	ml.recordf(fmtString, args...) | ||||
| 	ml.logger.Errorf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Info(args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Info(args ...interface{}) { | ||||
| 	ml.record(args...) | ||||
| 	ml.logger.Info(args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Infof(fmtString string, args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Infof(fmtString string, args ...interface{}) { | ||||
| 	ml.recordf(fmtString, args...) | ||||
| 	ml.logger.Infof(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Fatal(args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Fatal(args ...interface{}) { | ||||
| 	ml.record(args...) | ||||
| 	ml.logger.Fatal(args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Fatalf(fmtString string, args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Fatalf(fmtString string, args ...interface{}) { | ||||
| 	ml.recordf(fmtString, args...) | ||||
| 	ml.logger.Fatalf(fmtString, args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Warn(args ...interface{}) { | ||||
| func (ml *LogrusMachineLogger) Warn(args ...interface{}) { | ||||
| 	ml.record(args...) | ||||
| 	ml.logger.Warn(args...) | ||||
| } | ||||
| 
 | ||||
| func (ml LogrusMachineLogger) Warnf(fmtString string, args ...interface{}) { | ||||
| 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...)) | ||||
| } | ||||
|  |  | |||
|  | @ -11,31 +11,31 @@ import ( | |||
| ) | ||||
| 
 | ||||
| func TestDefaultLevelIsInfo(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| 	assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.InfoLevel) | ||||
| 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||
| 	assert.Equal(t, testLogger.Logger().Level, logrus.InfoLevel) | ||||
| } | ||||
| 
 | ||||
| func TestSetDebugToTrue(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||
| 	testLogger.SetDebug(true) | ||||
| 	assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.DebugLevel) | ||||
| 	assert.Equal(t, testLogger.Logger().Level, logrus.DebugLevel) | ||||
| } | ||||
| 
 | ||||
| func TestSetDebugToFalse(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||
| 	testLogger.SetDebug(true) | ||||
| 	testLogger.SetDebug(false) | ||||
| 	assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.InfoLevel) | ||||
| 	assert.Equal(t, testLogger.Logger().Level, logrus.InfoLevel) | ||||
| } | ||||
| 
 | ||||
| func TestSetSilenceOutput(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||
| 	testLogger.RedirectStdOutToStdErr() | ||||
| 	assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.ErrorLevel) | ||||
| 	assert.Equal(t, testLogger.Logger().Level, logrus.ErrorLevel) | ||||
| } | ||||
| 
 | ||||
| func TestDebug(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| func TestDebugOutput(t *testing.T) { | ||||
| 	testLogger := NewLogrusMachineLogger() | ||||
| 	testLogger.SetDebug(true) | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Debug("debug") }) | ||||
|  | @ -43,30 +43,42 @@ func TestDebug(t *testing.T) { | |||
| 	assert.Equal(t, result, "debug") | ||||
| } | ||||
| 
 | ||||
| func TestInfo(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| func TestInfoOutput(t *testing.T) { | ||||
| 	testLogger := NewLogrusMachineLogger() | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Info("info") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "info") | ||||
| } | ||||
| 
 | ||||
| func TestWarn(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| func TestWarnOutput(t *testing.T) { | ||||
| 	testLogger := NewLogrusMachineLogger() | ||||
| 
 | ||||
| 	result := captureOutput(testLogger, func() { testLogger.Warn("warn") }) | ||||
| 
 | ||||
| 	assert.Equal(t, result, "warn") | ||||
| } | ||||
| 
 | ||||
| func TestError(t *testing.T) { | ||||
| 	testLogger := NewMachineLogger() | ||||
| 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) | ||||
|  |  | |||
|  | @ -24,5 +24,5 @@ type MachineLogger interface { | |||
| 	Warn(args ...interface{}) | ||||
| 	Warnf(fmtString string, args ...interface{}) | ||||
| 
 | ||||
| 	Logger() interface{} | ||||
| 	History() []string | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue