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 | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	log.Logger = log.NewLogrusMachineLogger() | ||||||
| 	setDebugOutputLevel() | 	setDebugOutputLevel() | ||||||
| 	cli.AppHelpTemplate = AppHelpTemplate | 	cli.AppHelpTemplate = AppHelpTemplate | ||||||
| 	cli.CommandHelpTemplate = CommandHelpTemplate | 	cli.CommandHelpTemplate = CommandHelpTemplate | ||||||
|  | @ -149,6 +150,12 @@ func main() { | ||||||
| 			Name:   "native-ssh", | 			Name:   "native-ssh", | ||||||
| 			Usage:  "Use the native (Go-based) SSH implementation.", | 			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 { | 	if err := app.Run(os.Args); err != nil { | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"github.com/docker/machine/commands/mcndirs" | 	"github.com/docker/machine/commands/mcndirs" | ||||||
| 	"github.com/docker/machine/libmachine" | 	"github.com/docker/machine/libmachine" | ||||||
| 	"github.com/docker/machine/libmachine/cert" | 	"github.com/docker/machine/libmachine/cert" | ||||||
|  | 	"github.com/docker/machine/libmachine/crashreport" | ||||||
| 	"github.com/docker/machine/libmachine/host" | 	"github.com/docker/machine/libmachine/host" | ||||||
| 	"github.com/docker/machine/libmachine/log" | 	"github.com/docker/machine/libmachine/log" | ||||||
| 	"github.com/docker/machine/libmachine/mcnutils" | 	"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.GithubAPIToken = context.GlobalString("github-api-token") | ||||||
| 		api.Filestore.Path = context.GlobalString("storage-path") | 		api.Filestore.Path = context.GlobalString("storage-path") | ||||||
| 
 | 
 | ||||||
|  | 		crashreport.Configure(context.GlobalString("bugsnag-api-token")) | ||||||
|  | 
 | ||||||
| 		// TODO (nathanleclaire): These should ultimately be accessed
 | 		// TODO (nathanleclaire): These should ultimately be accessed
 | ||||||
| 		// through the libmachine client by the rest of the code and
 | 		// through the libmachine client by the rest of the code and
 | ||||||
| 		// not through their respective modules.  For now, however,
 | 		// 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 stop dev | ||||||
|     $ docker-machine start 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 ( | const ( | ||||||
| 	pluginOutPrefix     = "(%s) " | 	pluginOut           = "(%s) %s" | ||||||
| 	pluginErrPrefix     = "(%s) DBG | " | 	pluginErr           = "(%s) DBG | %s" | ||||||
| 	PluginEnvKey        = "MACHINE_PLUGIN_TOKEN" | 	PluginEnvKey        = "MACHINE_PLUGIN_TOKEN" | ||||||
| 	PluginEnvVal        = "42" | 	PluginEnvVal        = "42" | ||||||
| 	PluginEnvDriverName = "MACHINE_PLUGIN_DRIVER_NAME" | 	PluginEnvDriverName = "MACHINE_PLUGIN_DRIVER_NAME" | ||||||
|  | @ -210,9 +210,9 @@ func (lbp *Plugin) execServer() error { | ||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
| 		case out := <-stdOutCh: | 		case out := <-stdOutCh: | ||||||
| 			log.Info(fmt.Sprintf(pluginOutPrefix, lbp.MachineName), out) | 			log.Infof(pluginOut, lbp.MachineName, out) | ||||||
| 		case err := <-stdErrCh: | 		case err := <-stdErrCh: | ||||||
| 			log.Debug(fmt.Sprintf(pluginErrPrefix, lbp.MachineName), err) | 			log.Debugf(pluginErr, lbp.MachineName, err) | ||||||
| 		case _ = <-lbp.stopCh: | 		case _ = <-lbp.stopCh: | ||||||
| 			stopStdoutCh <- true | 			stopStdoutCh <- true | ||||||
| 			stopStderrCh <- true | 			stopStderrCh <- true | ||||||
|  |  | ||||||
|  | @ -78,11 +78,9 @@ func TestLocalBinaryPluginClose(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestExecServer(t *testing.T) { | func TestExecServer(t *testing.T) { | ||||||
| 	log.SetDebug(true) |  | ||||||
| 	machineName := "test" |  | ||||||
| 
 |  | ||||||
| 	logReader, logWriter := io.Pipe() | 	logReader, logWriter := io.Pipe() | ||||||
| 
 | 
 | ||||||
|  | 	log.SetDebug(true) | ||||||
| 	log.SetOutput(logWriter) | 	log.SetOutput(logWriter) | ||||||
| 
 | 
 | ||||||
| 	defer func() { | 	defer func() { | ||||||
|  | @ -98,6 +96,7 @@ func TestExecServer(t *testing.T) { | ||||||
| 		stderr: stderrReader, | 		stderr: stderrReader, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	machineName := "test" | ||||||
| 	lbp := &Plugin{ | 	lbp := &Plugin{ | ||||||
| 		MachineName: machineName, | 		MachineName: machineName, | ||||||
| 		Executor:    fe, | 		Executor:    fe, | ||||||
|  | @ -112,12 +111,10 @@ func TestExecServer(t *testing.T) { | ||||||
| 		finalErr <- lbp.execServer() | 		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) | 	logScanner := bufio.NewScanner(logReader) | ||||||
| 
 | 
 | ||||||
|  | 	// Write the ip address
 | ||||||
|  | 	expectedAddr := "127.0.0.1:12345" | ||||||
| 	if _, err := io.WriteString(stdoutWriter, expectedAddr+"\n"); err != nil { | 	if _, err := io.WriteString(stdoutWriter, expectedAddr+"\n"); err != nil { | ||||||
| 		t.Fatalf("Error attempting to write plugin address: %s", err) | 		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") | 		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 { | 	if _, err := io.WriteString(stdoutWriter, expectedPluginOut+"\n"); err != nil { | ||||||
| 		t.Fatalf("Error attempting to write to out in plugin: %s", err) | 		t.Fatalf("Error attempting to write to out in plugin: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	expectedOut := fmt.Sprintf(pluginOut, machineName, expectedPluginOut) | ||||||
| 	if logScanner.Scan(); logScanner.Text() != expectedOut { | 	if logScanner.Scan(); logScanner.Text() != expectedOut { | ||||||
| 		t.Fatalf("Output written to log was not what we expected\nexpected: %s\nactual:   %s", expectedOut, logScanner.Text()) | 		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 { | 	if _, err := io.WriteString(stderrWriter, expectedPluginErr+"\n"); err != nil { | ||||||
| 		t.Fatalf("Error attempting to write to err in plugin: %s", err) | 		t.Fatalf("Error attempting to write to err in plugin: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	expectedErr := fmt.Sprintf(pluginErr, machineName, expectedPluginErr) | ||||||
| 	if logScanner.Scan(); logScanner.Text() != expectedErr { | 	if logScanner.Scan(); logScanner.Text() != expectedErr { | ||||||
| 		t.Fatalf("Error written to log was not what we expected\nexpected: %s\nactual:   %s", expectedErr, logScanner.Text()) | 		t.Fatalf("Error written to log was not what we expected\nexpected: %s\nactual:   %s", expectedErr, logScanner.Text()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/machine/libmachine/auth" | 	"github.com/docker/machine/libmachine/auth" | ||||||
|  | 	"github.com/docker/machine/libmachine/crashreport" | ||||||
| 	"github.com/docker/machine/libmachine/drivers" | 	"github.com/docker/machine/libmachine/drivers" | ||||||
| 	"github.com/docker/machine/libmachine/engine" | 	"github.com/docker/machine/libmachine/engine" | ||||||
| 	"github.com/docker/machine/libmachine/log" | 	"github.com/docker/machine/libmachine/log" | ||||||
|  | @ -134,11 +135,13 @@ func (h *Host) Upgrade() error { | ||||||
| 
 | 
 | ||||||
| 	provisioner, err := provision.DetectProvisioner(h.Driver) | 	provisioner, err := provision.DetectProvisioner(h.Driver) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		crashreport.Send(err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Info("Upgrading docker...") | 	log.Info("Upgrading docker...") | ||||||
| 	if err := provisioner.Package("docker", pkgaction.Upgrade); err != nil { | 	if err := provisioner.Package("docker", pkgaction.Upgrade); err != nil { | ||||||
|  | 		crashreport.Send(err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"github.com/docker/machine/libmachine/auth" | 	"github.com/docker/machine/libmachine/auth" | ||||||
| 	"github.com/docker/machine/libmachine/cert" | 	"github.com/docker/machine/libmachine/cert" | ||||||
| 	"github.com/docker/machine/libmachine/check" | 	"github.com/docker/machine/libmachine/check" | ||||||
|  | 	"github.com/docker/machine/libmachine/crashreport" | ||||||
| 	"github.com/docker/machine/libmachine/drivers" | 	"github.com/docker/machine/libmachine/drivers" | ||||||
| 	"github.com/docker/machine/libmachine/engine" | 	"github.com/docker/machine/libmachine/engine" | ||||||
| 	"github.com/docker/machine/libmachine/host" | 	"github.com/docker/machine/libmachine/host" | ||||||
|  | @ -96,6 +97,18 @@ func (api *Client) Create(h *host.Host) error { | ||||||
| 
 | 
 | ||||||
| 	log.Info("Creating machine...") | 	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 { | 	if err := h.Driver.Create(); err != nil { | ||||||
| 		return fmt.Errorf("Error in driver during machine creation: %s", err) | 		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.Info("Docker is up and running!") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Debug("Reticulating splines...") |  | ||||||
| 
 |  | ||||||
| 	return nil | 	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" | import "io" | ||||||
| 
 | 
 | ||||||
| var logger MachineLogger | var Logger MachineLogger | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	logger = NewMachineLogger() | 	Logger = NewFmtMachineLogger() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RedirectStdOutToStdErr prevents any log from corrupting the output
 | // RedirectStdOutToStdErr prevents any log from corrupting the output
 | ||||||
| func RedirectStdOutToStdErr() { | func RedirectStdOutToStdErr() { | ||||||
| 	logger.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 Logger() interface{} { |  | ||||||
| 	return logger |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func SetDebug(debug bool) { | func SetDebug(debug bool) { | ||||||
| 	logger.SetDebug(debug) | 	Logger.SetDebug(debug) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func SetOutput(out io.Writer) { | 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 ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 
 | 
 | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
| 	"github.com/Sirupsen/logrus" | 	"github.com/Sirupsen/logrus" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type LogrusMachineLogger struct { | 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 := logrus.New() | ||||||
| 	logrusLogger.Level = logrus.InfoLevel | 	logrusLogger.Level = logrus.InfoLevel | ||||||
| 	logrusLogger.Formatter = new(MachineFormatter) | 	logrusLogger.Formatter = new(MachineFormatter) | ||||||
| 	return LogrusMachineLogger{logrusLogger} | 	return &LogrusMachineLogger{[]string{}, &sync.Mutex{}, logrusLogger} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RedirectStdOutToStdErr prevents any log from corrupting the output
 | // RedirectStdOutToStdErr prevents any log from corrupting the output
 | ||||||
| func (ml LogrusMachineLogger) RedirectStdOutToStdErr() { | func (ml *LogrusMachineLogger) RedirectStdOutToStdErr() { | ||||||
| 	ml.logger.Level = logrus.ErrorLevel | 	ml.logger.Level = logrus.ErrorLevel | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) SetDebug(debug bool) { | func (ml *LogrusMachineLogger) SetDebug(debug bool) { | ||||||
| 	if debug { | 	if debug { | ||||||
| 		ml.logger.Level = logrus.DebugLevel | 		ml.logger.Level = logrus.DebugLevel | ||||||
| 	} else { | 	} 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 | 	ml.logger.Out = out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Logger() interface{} { | func (ml *LogrusMachineLogger) Logger() *logrus.Logger { | ||||||
| 	return ml.logger | 	return ml.logger | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Debug(args ...interface{}) { | func (ml *LogrusMachineLogger) Debug(args ...interface{}) { | ||||||
|  | 	ml.record(args...) | ||||||
| 	ml.logger.Debug(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...) | 	ml.logger.Debugf(fmtString, args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Error(args ...interface{}) { | func (ml *LogrusMachineLogger) Error(args ...interface{}) { | ||||||
|  | 	ml.record(args...) | ||||||
| 	ml.logger.Error(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...) | 	ml.logger.Errorf(fmtString, args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Info(args ...interface{}) { | func (ml *LogrusMachineLogger) Info(args ...interface{}) { | ||||||
|  | 	ml.record(args...) | ||||||
| 	ml.logger.Info(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...) | 	ml.logger.Infof(fmtString, args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Fatal(args ...interface{}) { | func (ml *LogrusMachineLogger) Fatal(args ...interface{}) { | ||||||
|  | 	ml.record(args...) | ||||||
| 	ml.logger.Fatal(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...) | 	ml.logger.Fatalf(fmtString, args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ml LogrusMachineLogger) Warn(args ...interface{}) { | func (ml *LogrusMachineLogger) Warn(args ...interface{}) { | ||||||
|  | 	ml.record(args...) | ||||||
| 	ml.logger.Warn(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...) | 	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) { | func TestDefaultLevelIsInfo(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||||
| 	assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.InfoLevel) | 	assert.Equal(t, testLogger.Logger().Level, logrus.InfoLevel) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestSetDebugToTrue(t *testing.T) { | func TestSetDebugToTrue(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||||
| 	testLogger.SetDebug(true) | 	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) { | func TestSetDebugToFalse(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||||
| 	testLogger.SetDebug(true) | 	testLogger.SetDebug(true) | ||||||
| 	testLogger.SetDebug(false) | 	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) { | func TestSetSilenceOutput(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger().(*LogrusMachineLogger) | ||||||
| 	testLogger.RedirectStdOutToStdErr() | 	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) { | func TestDebugOutput(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger() | ||||||
| 	testLogger.SetDebug(true) | 	testLogger.SetDebug(true) | ||||||
| 
 | 
 | ||||||
| 	result := captureOutput(testLogger, func() { testLogger.Debug("debug") }) | 	result := captureOutput(testLogger, func() { testLogger.Debug("debug") }) | ||||||
|  | @ -43,30 +43,42 @@ func TestDebug(t *testing.T) { | ||||||
| 	assert.Equal(t, result, "debug") | 	assert.Equal(t, result, "debug") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestInfo(t *testing.T) { | func TestInfoOutput(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger() | ||||||
| 
 | 
 | ||||||
| 	result := captureOutput(testLogger, func() { testLogger.Info("info") }) | 	result := captureOutput(testLogger, func() { testLogger.Info("info") }) | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, result, "info") | 	assert.Equal(t, result, "info") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestWarn(t *testing.T) { | func TestWarnOutput(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger() | ||||||
| 
 | 
 | ||||||
| 	result := captureOutput(testLogger, func() { testLogger.Warn("warn") }) | 	result := captureOutput(testLogger, func() { testLogger.Warn("warn") }) | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, result, "warn") | 	assert.Equal(t, result, "warn") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestError(t *testing.T) { | func TestErrorOutput(t *testing.T) { | ||||||
| 	testLogger := NewMachineLogger() | 	testLogger := NewLogrusMachineLogger() | ||||||
| 
 | 
 | ||||||
| 	result := captureOutput(testLogger, func() { testLogger.Error("error") }) | 	result := captureOutput(testLogger, func() { testLogger.Error("error") }) | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, result, "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 { | func captureOutput(testLogger MachineLogger, lambda func()) string { | ||||||
| 	pipeReader, pipeWriter := io.Pipe() | 	pipeReader, pipeWriter := io.Pipe() | ||||||
| 	scanner := bufio.NewScanner(pipeReader) | 	scanner := bufio.NewScanner(pipeReader) | ||||||
|  |  | ||||||
|  | @ -24,5 +24,5 @@ type MachineLogger interface { | ||||||
| 	Warn(args ...interface{}) | 	Warn(args ...interface{}) | ||||||
| 	Warnf(fmtString string, args ...interface{}) | 	Warnf(fmtString string, args ...interface{}) | ||||||
| 
 | 
 | ||||||
| 	Logger() interface{} | 	History() []string | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue