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