diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 8492a67191..37794c5fe3 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -28,7 +28,9 @@
"github.com/docker/machine/drivers/vmwarevsphere/errors",
"github.com/docker/machine/libmachine",
"github.com/docker/machine/libmachine/auth",
+ "github.com/docker/machine/libmachine/bugsnag",
"github.com/docker/machine/libmachine/cert",
+ "github.com/docker/machine/libmachine/check",
"github.com/docker/machine/libmachine/drivers",
"github.com/docker/machine/libmachine/drivers/plugin",
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
@@ -39,6 +41,7 @@
"github.com/docker/machine/libmachine/hosttest",
"github.com/docker/machine/libmachine/libmachinetest",
"github.com/docker/machine/libmachine/log",
+ "github.com/docker/machine/libmachine/mcndockerclient",
"github.com/docker/machine/libmachine/mcnerror",
"github.com/docker/machine/libmachine/mcnflag",
"github.com/docker/machine/libmachine/mcnutils",
@@ -56,9 +59,22 @@
],
"Deps": [
{
- "ImportPath": "github.com/Sirupsen/logrus",
- "Comment": "v0.8.7-49-gcdaedc6",
- "Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
+ "ImportPath": "github.com/Sirupsen/logrus",
+ "Comment": "v0.8.7-49-gcdaedc6",
+ "Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
+ },
+ {
+ "ImportPath": "github.com/bugsnag/bugsnag-go",
+ "Comment": "v1.0.5-19-g02e9528",
+ "Rev": "02e952891c52fbcb15f113d90633897355783b6e"
+ },
+ {
+ "ImportPath": "github.com/bugsnag/osext",
+ "Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
+ },
+ {
+ "ImportPath": "github.com/bugsnag/panicwrap",
+ "Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
},
{
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
diff --git a/cmd/machine.go b/cmd/machine.go
index c330d5d10c..1657b55bee 100644
--- a/cmd/machine.go
+++ b/cmd/machine.go
@@ -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 {
diff --git a/commands/commands.go b/commands/commands.go
index 21cea662bf..3bbf54bec1 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -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"
@@ -104,6 +105,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,
diff --git a/docs/get-started.md b/docs/get-started.md
index c2f0945ee9..7ba3cd5c28 100644
--- a/docs/get-started.md
+++ b/docs/get-started.md
@@ -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.
+
diff --git a/libmachine/crashreport/crash_report.go b/libmachine/crashreport/crash_report.go
new file mode 100644
index 0000000000..ae2c05ca15
--- /dev/null
+++ b/libmachine/crashreport/crash_report.go
@@ -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))
+}
diff --git a/libmachine/crashreport/crash_report_logger.go b/libmachine/crashreport/crash_report_logger.go
new file mode 100644
index 0000000000..c124376939
--- /dev/null
+++ b/libmachine/crashreport/crash_report_logger.go
@@ -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)
+}
diff --git a/libmachine/drivers/plugin/localbinary/plugin.go b/libmachine/drivers/plugin/localbinary/plugin.go
index fd57e02cf9..81c91e02b1 100644
--- a/libmachine/drivers/plugin/localbinary/plugin.go
+++ b/libmachine/drivers/plugin/localbinary/plugin.go
@@ -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
diff --git a/libmachine/drivers/plugin/localbinary/plugin_test.go b/libmachine/drivers/plugin/localbinary/plugin_test.go
index 1f38d874e7..b469aa1578 100644
--- a/libmachine/drivers/plugin/localbinary/plugin_test.go
+++ b/libmachine/drivers/plugin/localbinary/plugin_test.go
@@ -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())
}
diff --git a/libmachine/host/host.go b/libmachine/host/host.go
index 1c7d04a8c8..2142d837f3 100644
--- a/libmachine/host/host.go
+++ b/libmachine/host/host.go
@@ -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
}
diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go
index 4249272ba7..7dd8464e41 100644
--- a/libmachine/libmachine.go
+++ b/libmachine/libmachine.go
@@ -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
+
}
diff --git a/libmachine/log/fmt_machine_logger.go b/libmachine/log/fmt_machine_logger.go
new file mode 100644
index 0000000000..13d8b51390
--- /dev/null
+++ b/libmachine/log/fmt_machine_logger.go
@@ -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{}
+}
diff --git a/libmachine/log/fmt_machine_logger_test.go b/libmachine/log/fmt_machine_logger_test.go
new file mode 100644
index 0000000000..690b78ec7a
--- /dev/null
+++ b/libmachine/log/fmt_machine_logger_test.go
@@ -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")
+}
diff --git a/libmachine/log/log.go b/libmachine/log/log.go
index 1ef5a552aa..eff2b11d4b 100644
--- a/libmachine/log/log.go
+++ b/libmachine/log/log.go
@@ -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()
}
diff --git a/libmachine/log/log_test.go b/libmachine/log/log_test.go
deleted file mode 100644
index cb7505bb84..0000000000
--- a/libmachine/log/log_test.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package log
-
-import (
- "testing"
-)
-
-func TestFormatterIsSet(t *testing.T) {
-
-}
diff --git a/libmachine/log/logrus_machine_logger.go b/libmachine/log/logrus_machine_logger.go
index df2d45bc96..f01cfd4dd9 100644
--- a/libmachine/log/logrus_machine_logger.go
+++ b/libmachine/log/logrus_machine_logger.go
@@ -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...))
+}
diff --git a/libmachine/log/logrus_machine_logger_test.go b/libmachine/log/logrus_machine_logger_test.go
index 7bbe4dd4fc..036d642229 100644
--- a/libmachine/log/logrus_machine_logger_test.go
+++ b/libmachine/log/logrus_machine_logger_test.go
@@ -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)
diff --git a/libmachine/log/machine_logger.go b/libmachine/log/machine_logger.go
index 4ef5adb922..7ab3fd9359 100644
--- a/libmachine/log/machine_logger.go
+++ b/libmachine/log/machine_logger.go
@@ -24,5 +24,5 @@ type MachineLogger interface {
Warn(args ...interface{})
Warnf(fmtString string, args ...interface{})
- Logger() interface{}
+ History() []string
}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/.travis.yml b/vendor/github.com/bugsnag/bugsnag-go/.travis.yml
new file mode 100644
index 0000000000..0d567658c6
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/.travis.yml
@@ -0,0 +1,11 @@
+sudo: false
+language: go
+
+go:
+ - 1.3
+ - 1.4
+ - 1.5
+ - tip
+
+script:
+ - make ci
diff --git a/vendor/github.com/bugsnag/bugsnag-go/CHANGELOG.md b/vendor/github.com/bugsnag/bugsnag-go/CHANGELOG.md
new file mode 100644
index 0000000000..79f736f00f
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/CHANGELOG.md
@@ -0,0 +1,22 @@
+1.0.4
+-----
+
+- Fix appengine integration broken by 1.0.3
+
+1.0.3
+-----
+
+- Allow any Logger with a Printf method.
+
+1.0.2
+-----
+
+- Use bugsnag copies of dependencies to avoid potential link rot
+
+1.0.1
+-----
+
+- gofmt/golint/govet docs improvements.
+
+1.0.0
+-----
diff --git a/vendor/github.com/bugsnag/bugsnag-go/CONTRIBUTING.md b/vendor/github.com/bugsnag/bugsnag-go/CONTRIBUTING.md
new file mode 100644
index 0000000000..a665b401e6
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/CONTRIBUTING.md
@@ -0,0 +1,78 @@
+Contributing
+============
+
+- [Fork](https://help.github.com/articles/fork-a-repo) the [notifier on github](https://github.com/bugsnag/bugsnag-go)
+- Build and test your changes
+- Commit and push until you are happy with your contribution
+- [Make a pull request](https://help.github.com/articles/using-pull-requests)
+- Thanks!
+
+
+Installing the go development environment
+-------------------------------------
+
+1. Install homebrew
+
+ ```
+ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
+ ```
+
+1. Install go
+
+ ```
+ brew install go --cross-compile-all
+ ```
+
+1. Configure `$GOPATH` in `~/.bashrc`
+
+ ```
+ export GOPATH="$HOME/go"
+ export PATH=$PATH:$GOPATH/bin
+ ```
+
+Installing the appengine development environment
+------------------------------------------------
+
+1. Follow the [Google instructions](https://cloud.google.com/appengine/downloads).
+
+Downloading the code
+--------------------
+
+You can download the code and its dependencies using
+
+```
+go get -t github.com/bugsnag/bugsnag-go
+```
+
+It will be put into "$GOPATH/src/github.com/bugsnag/bugsnag-go"
+
+Then install depend
+
+
+Running Tests
+-------------
+
+You can run the tests with
+
+```shell
+go test
+```
+
+If you've made significant changes, please also test the appengine integration with
+
+```shell
+goapp test
+```
+
+Releasing a New Version
+-----------------------
+
+If you are a project maintainer, you can build and release a new version of
+`bugsnag-go` as follows:
+
+1. Commit all your changes.
+2. Update the version number in `bugsnag.go`.
+3. Add an entry to `CHANGELOG.md` and update the README if necessary.
+4. commit tag and push
+
+ git commit -mv1.0.x && git tag v1.0.x && git push origin v1.0.x
diff --git a/vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt b/vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt
new file mode 100644
index 0000000000..3cb0ec0fff
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Bugsnag
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/bugsnag/bugsnag-go/Makefile b/vendor/github.com/bugsnag/bugsnag-go/Makefile
new file mode 100644
index 0000000000..70ec732320
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/Makefile
@@ -0,0 +1,30 @@
+TEST?=./...
+
+default: alldeps test
+
+deps:
+ go get -v -d ./...
+
+alldeps:
+ go get -v -d -t ./...
+
+updatedeps:
+ go get -v -d -u ./...
+
+test: alldeps
+ go test
+ @go vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
+ go get golang.org/x/tools/cmd/vet; \
+ fi
+ @go vet $(TEST) ; if [ $$? -eq 1 ]; then \
+ echo "go-vet: Issues running go vet ./..."; \
+ exit 1; \
+ fi
+
+ci: alldeps test
+
+bench:
+ go test --bench=.*
+
+
+.PHONY: bin checkversion ci default deps generate releasebin test testacc testrace updatedeps
diff --git a/vendor/github.com/bugsnag/bugsnag-go/README.md b/vendor/github.com/bugsnag/bugsnag-go/README.md
new file mode 100644
index 0000000000..60ddc9976e
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/README.md
@@ -0,0 +1,553 @@
+# Bugsnag Notifier for Golang
+[](https://github.com/bugsnag/bugsnag-go/releases)
+[](https://travis-ci.org/bugsnag/bugsnag-go)
+[](http://godoc.org/github.com/bugsnag/bugsnag-go)
+
+The Bugsnag Notifier for Golang gives you instant notification of panics, or
+unexpected errors, in your golang app. Any unhandled panics will trigger a
+notification to be sent to your Bugsnag project.
+
+[Bugsnag](http://bugsnag.com) captures errors in real-time from your web,
+mobile and desktop applications, helping you to understand and resolve them
+as fast as possible. [Create a free account](http://bugsnag.com) to start
+capturing exceptions from your applications.
+
+## How to Install
+
+1. Download the code
+
+ ```shell
+ go get github.com/bugsnag/bugsnag-go
+ ```
+
+### Using with net/http apps
+
+For a golang app based on [net/http](https://godoc.org/net/http), integrating
+Bugsnag takes two steps. You should also use these instructions if you're using
+the [gorilla toolkit](http://www.gorillatoolkit.org/), or the
+[pat](https://github.com/bmizerany/pat/) muxer.
+
+1. Configure bugsnag at the start of your `main()` function:
+
+ ```go
+ import "github.com/bugsnag/bugsnag-go"
+
+ func main() {
+ bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "YOUR_API_KEY_HERE",
+ ReleaseStage: "production",
+ // more configuration options
+ })
+
+ // rest of your program.
+ }
+ ```
+
+2. Wrap your server in a [bugsnag.Handler](https://godoc.org/github.com/bugsnag/bugsnag-go/#Handler)
+
+ ```go
+ // a. If you're using the builtin http mux, you can just pass
+ // bugsnag.Handler(nil) to http.ListenAndServer
+ http.ListenAndServe(":8080", bugsnag.Handler(nil))
+
+ // b. If you're creating a server manually yourself, you can set
+ // its handlers the same way
+ srv := http.Server{
+ Handler: bugsnag.Handler(nil)
+ }
+
+ // c. If you're not using the builtin http mux, wrap your own handler
+ // (though make sure that it doesn't already catch panics)
+ http.ListenAndServe(":8080", bugsnag.Handler(handler))
+ ```
+
+### Using with Revel apps
+
+There are two steps to get panic handling in [revel](https://revel.github.io) apps.
+
+1. Add the `bugsnagrevel.Filter` immediately after the `revel.PanicFilter` in `app/init.go`:
+
+ ```go
+
+ import "github.com/bugsnag/bugsnag-go/revel"
+
+ revel.Filters = []revel.Filter{
+ revel.PanicFilter,
+ bugsnagrevel.Filter,
+ // ...
+ }
+ ```
+
+2. Set bugsnag.apikey in the top section of `conf/app.conf`.
+
+ ```
+ module.static=github.com/revel/revel/modules/static
+
+ bugsnag.apikey=YOUR_API_KEY_HERE
+
+ [dev]
+ ```
+
+### Using with martini apps
+
+1. Add `bugsnagmartini.AutoNotify` immediately after the `martini.Recovery` middleware in `main.go`.
+This causes unhandled panics to notify bugsnag.
+
+ ```go
+
+ import "github.com/bugsnag/bugsnag-go/martini"
+
+ func main() {
+
+ m.Use(martini.Recover()
+
+ m.Use(bugsnagmartini.AutoNotify(bugsnag.Configuration{
+ APIKey: "YOUR_API_KEY_HERE",
+ }))
+ }
+ ```
+
+2. Use `bugsnag` from the context injection if you need to notify about non-fatal errors.
+
+ ```
+ func MyHandler(r *http.Request, bugsnag *bugsnag.Notifier) string {
+ bugsnag.Notify(err);
+ }
+ ```
+
+### Using with Google App Engine
+
+1. Configure bugsnag at the start of your `init()` function:
+
+ ```go
+ import "github.com/bugsnag/bugsnag-go"
+
+ func init() {
+ bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "YOUR_API_KEY_HERE",
+ })
+
+ // ...
+ }
+ ```
+
+2. Wrap *every* http.Handler or http.HandlerFunc with Bugsnag:
+
+ ```go
+ // a. If you're using HandlerFuncs
+ http.HandleFunc("/", bugsnag.HandlerFunc(
+ func (w http.ResponseWriter, r *http.Request) {
+ // ...
+ }))
+
+ // b. If you're using Handlers
+ http.Handle("/", bugsnag.Handler(myHttpHandler))
+ ```
+
+3. In order to use Bugsnag, you must provide the current
+[`appengine.Context`](https://developers.google.com/appengine/docs/go/reference#Context), or
+current `*http.Request` as rawData (This is done automatically for `bugsnag.Handler` and `bugsnag.HandlerFunc`).
+The easiest way to do this is to create a new instance of the notifier.
+
+ ```go
+ c := appengine.NewContext(r)
+ notifier := bugsnag.New(c)
+
+ if err != nil {
+ notifier.Notify(err)
+ }
+
+ go func () {
+ defer notifier.Recover()
+
+ // ...
+ }()
+ ```
+
+
+## Notifying Bugsnag manually
+
+Bugsnag will automatically handle any panics that crash your program and notify
+you of them. If you've integrated with `revel` or `net/http`, then you'll also
+be notified of any panics() that happen while processing a request.
+
+Sometimes however it's useful to manually notify Bugsnag of a problem. To do this,
+call [`bugsnag.Notify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Notify)
+
+```go
+if err != nil {
+ bugsnag.Notify(err)
+}
+```
+
+### Manual panic handling
+
+To avoid a panic in a goroutine from crashing your entire app, you can use
+[`bugsnag.Recover()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
+to stop a panic from unwinding the stack any further. When `Recover()` is hit,
+it will send any current panic to Bugsnag and then stop panicking. This is
+most useful at the start of a goroutine:
+
+```go
+go func() {
+ defer bugsnag.Recover()
+
+ // ...
+}()
+```
+
+Alternatively you can use
+[`bugsnag.AutoNotify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
+to notify bugsnag of a panic while letting the program continue to panic. This
+is useful if you're using a Framework that already has some handling of panics
+and you are retrofitting bugsnag support.
+
+```go
+defer bugsnag.AutoNotify()
+```
+
+## Sending Custom Data
+
+Most functions in the Bugsnag API, including `bugsnag.Notify()`,
+`bugsnag.Recover()`, `bugsnag.AutoNotify()`, and `bugsnag.Handler()` let you
+attach data to the notifications that they send. To do this you pass in rawData,
+which can be any of the supported types listed here. To add support for more
+types of rawData see [OnBeforeNotify](#custom-data-with-onbeforenotify).
+
+### Custom MetaData
+
+Custom metaData appears as tabs on Bugsnag.com. You can set it by passing
+a [`bugsnag.MetaData`](https://godoc.org/github.com/bugsnag/bugsnag-go/#MetaData)
+object as rawData.
+
+```go
+bugsnag.Notify(err,
+ bugsnag.MetaData{
+ "Account": {
+ "Name": Account.Name,
+ "Paying": Account.Plan.Premium,
+ },
+ })
+```
+
+### Request data
+
+Bugsnag can extract interesting data from
+[`*http.Request`](https://godoc.org/net/http/#Request) objects, and
+[`*revel.Controller`](https://godoc.org/github.com/revel/revel/#Controller)
+objects. These are automatically passed in when handling panics, and you can
+pass them yourself.
+
+```go
+func (w http.ResponseWriter, r *http.Request) {
+ bugsnag.Notify(err, r)
+}
+```
+
+### User data
+
+User data is searchable, and the `Id` powers the count of users affected. You
+can set which user an error affects by passing a
+[`bugsnag.User`](https://godoc.org/github.com/bugsnag/bugsnag-go/#User) object as
+rawData.
+
+```go
+bugsnag.Notify(err,
+ bugsnag.User{Id: "1234", Name: "Conrad", Email: "me@cirw.in"})
+```
+
+### Error Class
+
+Errors in your Bugsnag dashboard are grouped by their "error class" and by line number.
+You can override the error class by passing a
+[`bugsnag.ErrorClass`](https://godoc.org/github.com/bugsnag/bugsnag-go/#ErrorClass) object as
+rawData.
+
+```go
+bugsnag.Notify(err, bugsnag.ErrorClass{"I/O Timeout"})
+```
+
+### Context
+
+The context shows up prominently in the list view so that you can get an idea
+of where a problem occurred. You can set it by passing a
+[`bugsnag.Context`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Context)
+object as rawData.
+
+```go
+bugsnag.Notify(err, bugsnag.Context{"backgroundJob"})
+```
+
+### Severity
+
+Bugsnag supports three severities, `SeverityError`, `SeverityWarning`, and `SeverityInfo`.
+You can set the severity of an error by passing one of these objects as rawData.
+
+```go
+bugsnag.Notify(err, bugsnag.SeverityInfo)
+```
+
+## Configuration
+
+You must call `bugsnag.Configure()` at the start of your program to use Bugsnag, you pass it
+a [`bugsnag.Configuration`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Configuration) object
+containing any of the following values.
+
+### APIKey
+
+The Bugsnag API key can be found on your [Bugsnag dashboard](https://bugsnag.com) under "Settings".
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "YOUR_API_KEY_HERE",
+})
+```
+
+### Endpoint
+
+The Bugsnag endpoint defaults to `https://notify.bugsnag.com/`. If you're using Bugsnag enterprise,
+you should set this to the endpoint of your local instance.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ Endpoint: "http://bugsnag.internal:49000/",
+})
+```
+
+### ReleaseStage
+
+The ReleaseStage tracks where your app is deployed. You should set this to `production`, `staging`,
+`development` or similar as appropriate.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ ReleaseStage: "development",
+})
+```
+
+### NotifyReleaseStages
+
+The list of ReleaseStages to notify in. By default Bugsnag will notify you in all release stages, but
+you can use this to silence development errors.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ NotifyReleaseStages: []string{"production", "staging"},
+})
+```
+
+### AppVersion
+
+If you use a versioning scheme for deploys of your app, Bugsnag can use the `AppVersion` to only
+re-open errors if they occur in later version of the app.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ AppVersion: "1.2.3",
+})
+```
+
+### Hostname
+
+The hostname is used to track where exceptions are coming from in the Bugsnag dashboard. The
+default value is obtained from `os.Hostname()` so you won't often need to change this.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ Hostname: "go1",
+})
+```
+
+### ProjectPackages
+
+In order to determine where a crash happens Bugsnag needs to know which packages you consider to
+be part of your app (as opposed to a library). By default this is set to `[]string{"main*"}`. Strings
+are matched to package names using [`filepath.Match`](http://godoc.org/path/filepath#Match).
+
+For matching subpackages within a package you may use the `**` notation. For example, `github.com/domain/package/**` will match all subpackages under `package/`.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ ProjectPackages: []string{"main", "github.com/domain/myapp/*"},
+}
+```
+
+### ParamsFilters
+
+Sometimes sensitive data is accidentally included in Bugsnag MetaData. You can remove it by
+setting `ParamsFilters`. Any key in the `MetaData` that includes any string in the filters
+will be redacted. The default is `[]string{"password", "secret"}`, which prevents fields like
+`password`, `password_confirmation` and `secret_answer` from being sent.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ ParamsFilters: []string{"password", "secret"},
+}
+```
+
+### Logger
+
+The Logger to write to in case of an error inside Bugsnag. This defaults to the global logger.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ Logger: app.Logger,
+}
+```
+
+### PanicHandler
+
+The first time Bugsnag is configured, it wraps the running program in a panic
+handler using [panicwrap](http://godoc.org/github.com/ConradIrwin/panicwrap). This
+forks a sub-process which monitors unhandled panics. To prevent this, set
+`PanicHandler` to `func() {}` the first time you call
+`bugsnag.Configure`. This will prevent bugsnag from being able to notify you about
+unhandled panics.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ PanicHandler: func() {},
+})
+```
+
+### Synchronous
+
+Bugsnag usually starts a new goroutine before sending notifications. This means
+that notifications can be lost if you do a bugsnag.Notify and then immediately
+os.Exit. To avoid this problem, set Bugsnag to Synchronous (or just `panic()`
+instead ;).
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ Synchronous: true
+})
+```
+
+Or just for one error:
+
+```go
+bugsnag.Notify(err, bugsnag.Configuration{Synchronous: true})
+```
+
+### Transport
+
+The transport configures how Bugsnag makes http requests. By default we use
+[`http.DefaultTransport`](http://godoc.org/net/http#RoundTripper) which handles
+HTTP proxies automatically using the `$HTTP_PROXY` environment variable.
+
+```go
+bugsnag.Configure(bugsnag.Configuration{
+ Transport: http.DefaultTransport,
+})
+```
+
+## Custom data with OnBeforeNotify
+
+While it's nice that you can pass `MetaData` directly into `bugsnag.Notify`,
+`bugsnag.AutoNotify`, and `bugsnag.Recover`, this can be a bit cumbersome and
+inefficient — you're constructing the meta-data whether or not it will actually
+be used. A better idea is to pass raw data in to these functions, and add an
+`OnBeforeNotify` filter that converts them into `MetaData`.
+
+For example, lets say our system processes jobs:
+
+```go
+type Job struct{
+ Retry bool
+ UserId string
+ UserEmail string
+ Name string
+ Params map[string]string
+}
+```
+
+You can pass a job directly into Bugsnag.notify:
+
+```go
+bugsnag.Notify(err, job)
+```
+
+And then add a filter to extract information from that job and attach it to the
+Bugsnag event:
+
+```go
+bugsnag.OnBeforeNotify(
+ func(event *bugsnag.Event, config *bugsnag.Configuration) error {
+
+ // Search all the RawData for any *Job pointers that we're passed in
+ // to bugsnag.Notify() and friends.
+ for _, datum := range event.RawData {
+ if job, ok := datum.(*Job); ok {
+ // don't notify bugsnag about errors in retries
+ if job.Retry {
+ return fmt.Errorf("not notifying about retried jobs")
+ }
+
+ // add the job as a tab on Bugsnag.com
+ event.MetaData.AddStruct("Job", job)
+
+ // set the user correctly
+ event.User = &User{Id: job.UserId, Email: job.UserEmail}
+ }
+ }
+
+ // continue notifying as normal
+ return nil
+ })
+```
+
+## Advanced Usage
+
+If you want to have multiple different configurations around in one program,
+you can use `bugsnag.New()` to create multiple independent instances of
+Bugsnag. You can use these without calling `bugsnag.Configure()`, but bear in
+mind that until you call `bugsnag.Configure()` unhandled panics will not be
+sent to bugsnag.
+
+```go
+notifier := bugsnag.New(bugsnag.Configuration{
+ APIKey: "YOUR_OTHER_API_KEY",
+})
+```
+
+In fact any place that lets you pass in `rawData` also allows you to pass in
+configuration. For example to send http errors to one bugsnag project, you
+could do:
+
+```go
+bugsnag.Handler(nil, bugsnag.Configuration{APIKey: "YOUR_OTHER_API_KEY"})
+```
+
+### GroupingHash
+
+If you need to override Bugsnag's grouping algorithm, you can set the
+`GroupingHash` in an `OnBeforeNotify`:
+
+```go
+bugsnag.OnBeforeNotify(
+ func (event *bugsnag.Event, config *bugsnag.Configuration) error {
+ event.GroupingHash = calculateGroupingHash(event)
+ return nil
+ })
+```
+
+### Skipping lines in stacktrace
+
+If you have your own logging wrapper all of your errors will appear to
+originate from inside it. You can avoid this problem by constructing
+an error with a stacktrace manually, and then passing that to Bugsnag.notify:
+
+```go
+import (
+ "github.com/bugsnag/bugsnag-go"
+ "github.com/bugsnag/bugsnag-go/errors"
+)
+
+func LogError(e error) {
+ // 1 removes one line of stacktrace, so the caller of LogError
+ // will be at the top.
+ e = errors.New(e, 1)
+ bugsnag.Notify(e)
+}
+```
+
diff --git a/vendor/github.com/bugsnag/bugsnag-go/appengine.go b/vendor/github.com/bugsnag/bugsnag-go/appengine.go
new file mode 100644
index 0000000000..81e25069cb
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/appengine.go
@@ -0,0 +1,81 @@
+// +build appengine
+
+package bugsnag
+
+import (
+ "appengine"
+ "appengine/urlfetch"
+ "appengine/user"
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func defaultPanicHandler() {}
+
+func init() {
+ OnBeforeNotify(appengineMiddleware)
+}
+
+func appengineMiddleware(event *Event, config *Configuration) (err error) {
+ var c appengine.Context
+
+ for _, datum := range event.RawData {
+ if r, ok := datum.(*http.Request); ok {
+ c = appengine.NewContext(r)
+ break
+ } else if context, ok := datum.(appengine.Context); ok {
+ c = context
+ break
+ }
+ }
+
+ if c == nil {
+ return fmt.Errorf("No appengine context given")
+ }
+
+ // You can only use the builtin http library if you pay for appengine,
+ // so we use the appengine urlfetch service instead.
+ config.Transport = &urlfetch.Transport{
+ Context: c,
+ }
+
+ // Anything written to stderr/stdout is discarded, so lets log to the request.
+
+ if configuredLogger, ok := config.Logger.(*log.Logger); ok {
+ config.Logger = log.New(appengineWriter{c}, configuredLogger.Prefix(), configuredLogger.Flags())
+ } else {
+ config.Logger = log.New(appengineWriter{c}, log.Prefix(), log.Flags())
+ }
+
+ // Set the releaseStage appropriately
+ if config.ReleaseStage == "" {
+ if appengine.IsDevAppServer() {
+ config.ReleaseStage = "development"
+ } else {
+ config.ReleaseStage = "production"
+ }
+ }
+
+ if event.User == nil {
+ u := user.Current(c)
+ if u != nil {
+ event.User = &User{
+ Id: u.ID,
+ Email: u.Email,
+ }
+ }
+ }
+
+ return nil
+}
+
+// Convert an appengine.Context into an io.Writer so we can create a log.Logger.
+type appengineWriter struct {
+ appengine.Context
+}
+
+func (c appengineWriter) Write(b []byte) (int, error) {
+ c.Warningf(string(b))
+ return len(b), nil
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/bugsnag.go b/vendor/github.com/bugsnag/bugsnag-go/bugsnag.go
new file mode 100644
index 0000000000..e9bbf4656a
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/bugsnag.go
@@ -0,0 +1,131 @@
+package bugsnag
+
+import (
+ "github.com/bugsnag/bugsnag-go/errors"
+ "log"
+ "net/http"
+ "os"
+ "sync"
+
+ // Fixes a bug with SHA-384 intermediate certs on some platforms.
+ // - https://github.com/bugsnag/bugsnag-go/issues/9
+ _ "crypto/sha512"
+)
+
+// The current version of bugsnag-go.
+const VERSION = "1.0.3"
+
+var once sync.Once
+var middleware middlewareStack
+
+// The configuration for the default bugsnag notifier.
+var Config Configuration
+
+var defaultNotifier = Notifier{&Config, nil}
+
+// Configure Bugsnag. The only required setting is the APIKey, which can be
+// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
+// is also responsible for installing the global panic handler, so it should be
+// called as early as possible in your initialization process.
+func Configure(config Configuration) {
+ Config.update(&config)
+ once.Do(Config.PanicHandler)
+}
+
+// Notify sends an error to Bugsnag along with the current stack trace. The
+// rawData is used to send extra information along with the error. For example
+// you can pass the current http.Request to Bugsnag to see information about it
+// in the dashboard, or set the severity of the notification.
+func Notify(err error, rawData ...interface{}) error {
+ return defaultNotifier.Notify(errors.New(err, 1), rawData...)
+}
+
+// AutoNotify logs a panic on a goroutine and then repanics.
+// It should only be used in places that have existing panic handlers further
+// up the stack. See bugsnag.Recover(). The rawData is used to send extra
+// information along with any panics that are handled this way.
+// Usage: defer bugsnag.AutoNotify()
+func AutoNotify(rawData ...interface{}) {
+ if err := recover(); err != nil {
+ rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityError)
+ defaultNotifier.Notify(errors.New(err, 2), rawData...)
+ panic(err)
+ }
+}
+
+// Recover logs a panic on a goroutine and then recovers.
+// The rawData is used to send extra information along with
+// any panics that are handled this way
+// Usage: defer bugsnag.Recover()
+func Recover(rawData ...interface{}) {
+ if err := recover(); err != nil {
+ rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityWarning)
+ defaultNotifier.Notify(errors.New(err, 2), rawData...)
+ }
+}
+
+// OnBeforeNotify adds a callback to be run before a notification is sent to
+// Bugsnag. It can be used to modify the event or its MetaData. Changes made
+// to the configuration are local to notifying about this event. To prevent the
+// event from being sent to Bugsnag return an error, this error will be
+// returned from bugsnag.Notify() and the event will not be sent.
+func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
+ middleware.OnBeforeNotify(callback)
+}
+
+// Handler creates an http Handler that notifies Bugsnag any panics that
+// happen. It then repanics so that the default http Server panic handler can
+// handle the panic too. The rawData is used to send extra information along
+// with any panics that are handled this way.
+func Handler(h http.Handler, rawData ...interface{}) http.Handler {
+ notifier := New(rawData...)
+ if h == nil {
+ h = http.DefaultServeMux
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ defer notifier.AutoNotify(r)
+ h.ServeHTTP(w, r)
+ })
+}
+
+// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
+// panics that happen. It then repanics so that the default http Server panic
+// handler can handle the panic too. The rawData is used to send extra
+// information along with any panics that are handled this way. If you have
+// already wrapped your http server using bugsnag.Handler() you don't also need
+// to wrap each HandlerFunc.
+func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
+ notifier := New(rawData...)
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ defer notifier.AutoNotify(r)
+ h(w, r)
+ }
+}
+
+func init() {
+ // Set up builtin middlewarez
+ OnBeforeNotify(httpRequestMiddleware)
+
+ // Default configuration
+ Config.update(&Configuration{
+ APIKey: "",
+ Endpoint: "https://notify.bugsnag.com/",
+ Hostname: "",
+ AppVersion: "",
+ ReleaseStage: "",
+ ParamsFilters: []string{"password", "secret"},
+ // * for app-engine
+ ProjectPackages: []string{"main*"},
+ NotifyReleaseStages: nil,
+ Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
+ PanicHandler: defaultPanicHandler,
+ Transport: http.DefaultTransport,
+ })
+
+ hostname, err := os.Hostname()
+ if err == nil {
+ Config.Hostname = hostname
+ }
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/configuration.go b/vendor/github.com/bugsnag/bugsnag-go/configuration.go
new file mode 100644
index 0000000000..ad1d42db9c
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/configuration.go
@@ -0,0 +1,169 @@
+package bugsnag
+
+import (
+ "log"
+ "net/http"
+ "path/filepath"
+ "strings"
+)
+
+// Configuration sets up and customizes communication with the Bugsnag API.
+type Configuration struct {
+ // Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
+ // find this by clicking Settings on https://bugsnag.com/.
+ APIKey string
+ // The Endpoint to notify about crashes. This defaults to
+ // "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
+ // set it to your internal Bugsnag endpoint.
+ Endpoint string
+
+ // The current release stage. This defaults to "production" and is used to
+ // filter errors in the Bugsnag dashboard.
+ ReleaseStage string
+ // The currently running version of the app. This is used to filter errors
+ // in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
+ // resolved errors if they happen in different app versions.
+ AppVersion string
+ // The hostname of the current server. This defaults to the return value of
+ // os.Hostname() and is graphed in the Bugsnag dashboard.
+ Hostname string
+
+ // The Release stages to notify in. If you set this then bugsnag-go will
+ // only send notifications to Bugsnag if the ReleaseStage is listed here.
+ NotifyReleaseStages []string
+
+ // packages that are part of your app. Bugsnag uses this to determine how
+ // to group errors and how to display them on your dashboard. You should
+ // include any packages that are part of your app, and exclude libraries
+ // and helpers. You can list wildcards here, and they'll be expanded using
+ // filepath.Glob. The default value is []string{"main*"}
+ ProjectPackages []string
+
+ // Any meta-data that matches these filters will be marked as [REDACTED]
+ // before sending a Notification to Bugsnag. It defaults to
+ // []string{"password", "secret"} so that request parameters like password,
+ // password_confirmation and auth_secret will not be sent to Bugsnag.
+ ParamsFilters []string
+
+ // The PanicHandler is used by Bugsnag to catch unhandled panics in your
+ // application. The default panicHandler uses mitchellh's panicwrap library,
+ // and you can disable this feature by passing an empty: func() {}
+ PanicHandler func()
+
+ // The logger that Bugsnag should log to. Uses the same defaults as go's
+ // builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
+ // of an error, and when any error occurs inside the library itself.
+ Logger interface {
+ Printf(format string, v ...interface{}) // limited to the functions used
+ }
+ // The http Transport to use, defaults to the default http Transport. This
+ // can be configured if you are in an environment like Google App Engine
+ // that has stringent conditions on making http requests.
+ Transport http.RoundTripper
+ // Whether bugsnag should notify synchronously. This defaults to false which
+ // causes bugsnag-go to spawn a new goroutine for each notification.
+ Synchronous bool
+ // TODO: remember to update the update() function when modifying this struct
+}
+
+func (config *Configuration) update(other *Configuration) *Configuration {
+ if other.APIKey != "" {
+ config.APIKey = other.APIKey
+ }
+ if other.Endpoint != "" {
+ config.Endpoint = other.Endpoint
+ }
+ if other.Hostname != "" {
+ config.Hostname = other.Hostname
+ }
+ if other.AppVersion != "" {
+ config.AppVersion = other.AppVersion
+ }
+ if other.ReleaseStage != "" {
+ config.ReleaseStage = other.ReleaseStage
+ }
+ if other.ParamsFilters != nil {
+ config.ParamsFilters = other.ParamsFilters
+ }
+ if other.ProjectPackages != nil {
+ config.ProjectPackages = other.ProjectPackages
+ }
+ if other.Logger != nil {
+ config.Logger = other.Logger
+ }
+ if other.NotifyReleaseStages != nil {
+ config.NotifyReleaseStages = other.NotifyReleaseStages
+ }
+ if other.PanicHandler != nil {
+ config.PanicHandler = other.PanicHandler
+ }
+ if other.Transport != nil {
+ config.Transport = other.Transport
+ }
+ if other.Synchronous {
+ config.Synchronous = true
+ }
+
+ return config
+}
+
+func (config *Configuration) merge(other *Configuration) *Configuration {
+ return config.clone().update(other)
+}
+
+func (config *Configuration) clone() *Configuration {
+ clone := *config
+ return &clone
+}
+
+func (config *Configuration) isProjectPackage(pkg string) bool {
+ for _, p := range config.ProjectPackages {
+ if d, f := filepath.Split(p); f == "**" {
+ if strings.HasPrefix(pkg, d) {
+ return true
+ }
+ }
+
+ if match, _ := filepath.Match(p, pkg); match {
+ return true
+ }
+ }
+ return false
+}
+
+func (config *Configuration) stripProjectPackages(file string) string {
+ for _, p := range config.ProjectPackages {
+ if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
+ p = p[:len(p)-1]
+ } else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
+ p = p[:len(p)-2]
+ } else {
+ p = p + "/"
+ }
+ if strings.HasPrefix(file, p) {
+ return strings.TrimPrefix(file, p)
+ }
+ }
+
+ return file
+}
+
+func (config *Configuration) logf(fmt string, args ...interface{}) {
+ if config != nil && config.Logger != nil {
+ config.Logger.Printf(fmt, args...)
+ } else {
+ log.Printf(fmt, args...)
+ }
+}
+
+func (config *Configuration) notifyInReleaseStage() bool {
+ if config.NotifyReleaseStages == nil {
+ return true
+ }
+ for _, r := range config.NotifyReleaseStages {
+ if r == config.ReleaseStage {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/doc.go b/vendor/github.com/bugsnag/bugsnag-go/doc.go
new file mode 100644
index 0000000000..827e03b8be
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/doc.go
@@ -0,0 +1,69 @@
+/*
+Package bugsnag captures errors in real-time and reports them to Bugsnag (http://bugsnag.com).
+
+Using bugsnag-go is a three-step process.
+
+1. As early as possible in your program configure the notifier with your APIKey. This sets up
+handling of panics that would otherwise crash your app.
+
+ func init() {
+ bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "YOUR_API_KEY_HERE",
+ })
+ }
+
+2. Add bugsnag to places that already catch panics. For example you should add it to the HTTP server
+when you call ListenAndServer:
+
+ http.ListenAndServe(":8080", bugsnag.Handler(nil))
+
+If that's not possible, for example because you're using Google App Engine, you can also wrap each
+HTTP handler manually:
+
+ http.HandleFunc("/" bugsnag.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
+ ...
+ })
+
+3. To notify Bugsnag of an error that is not a panic, pass it to bugsnag.Notify. This will also
+log the error message using the configured Logger.
+
+ if err != nil {
+ bugsnag.Notify(err)
+ }
+
+For detailed integration instructions see https://bugsnag.com/docs/notifiers/go.
+
+Configuration
+
+The only required configuration is the Bugsnag API key which can be obtained by clicking "Settings"
+on the top of https://bugsnag.com/ after signing up. We also recommend you set the ReleaseStage
+and AppVersion if these make sense for your deployment workflow.
+
+RawData
+
+If you need to attach extra data to Bugsnag notifications you can do that using
+the rawData mechanism. Most of the functions that send errors to Bugsnag allow
+you to pass in any number of interface{} values as rawData. The rawData can
+consist of the Severity, Context, User or MetaData types listed below, and
+there is also builtin support for *http.Requests.
+
+ bugsnag.Notify(err, bugsnag.SeverityError)
+
+If you want to add custom tabs to your bugsnag dashboard you can pass any value in as rawData,
+and then process it into the event's metadata using a bugsnag.OnBeforeNotify() hook.
+
+ bugsnag.Notify(err, account)
+
+ bugsnag.OnBeforeNotify(func (e *bugsnag.Event, c *bugsnag.Configuration) {
+ for datum := range e.RawData {
+ if account, ok := datum.(Account); ok {
+ e.MetaData.Add("account", "name", account.Name)
+ e.MetaData.Add("account", "url", account.URL)
+ }
+ }
+ })
+
+If necessary you can pass Configuration in as rawData, or modify the Configuration object passed
+into OnBeforeNotify hooks. Configuration passed in this way only affects the current notification.
+*/
+package bugsnag
diff --git a/vendor/github.com/bugsnag/bugsnag-go/errors/README.md b/vendor/github.com/bugsnag/bugsnag-go/errors/README.md
new file mode 100644
index 0000000000..8d8e097aa7
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/errors/README.md
@@ -0,0 +1,6 @@
+Adds stacktraces to errors in golang.
+
+This was made to help build the Bugsnag notifier but can be used standalone if
+you like to have stacktraces on errors.
+
+See [Godoc](https://godoc.org/github.com/bugsnag/bugsnag-go/errors) for the API docs.
diff --git a/vendor/github.com/bugsnag/bugsnag-go/errors/error.go b/vendor/github.com/bugsnag/bugsnag-go/errors/error.go
new file mode 100644
index 0000000000..0081c0a80c
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/errors/error.go
@@ -0,0 +1,90 @@
+// Package errors provides errors that have stack-traces.
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "runtime"
+)
+
+// The maximum number of stackframes on any error.
+var MaxStackDepth = 50
+
+// Error is an error with an attached stacktrace. It can be used
+// wherever the builtin error interface is expected.
+type Error struct {
+ Err error
+ stack []uintptr
+ frames []StackFrame
+}
+
+// New makes an Error from the given value. If that value is already an
+// error then it will be used directly, if not, it will be passed to
+// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
+// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
+func New(e interface{}, skip int) *Error {
+ var err error
+
+ switch e := e.(type) {
+ case *Error:
+ return e
+ case error:
+ err = e
+ default:
+ err = fmt.Errorf("%v", e)
+ }
+
+ stack := make([]uintptr, MaxStackDepth)
+ length := runtime.Callers(2+skip, stack[:])
+ return &Error{
+ Err: err,
+ stack: stack[:length],
+ }
+}
+
+// Errorf creates a new error with the given message. You can use it
+// as a drop-in replacement for fmt.Errorf() to provide descriptive
+// errors in return values.
+func Errorf(format string, a ...interface{}) *Error {
+ return New(fmt.Errorf(format, a...), 1)
+}
+
+// Error returns the underlying error's message.
+func (err *Error) Error() string {
+ return err.Err.Error()
+}
+
+// Stack returns the callstack formatted the same way that go does
+// in runtime/debug.Stack()
+func (err *Error) Stack() []byte {
+ buf := bytes.Buffer{}
+
+ for _, frame := range err.StackFrames() {
+ buf.WriteString(frame.String())
+ }
+
+ return buf.Bytes()
+}
+
+// StackFrames returns an array of frames containing information about the
+// stack.
+func (err *Error) StackFrames() []StackFrame {
+ if err.frames == nil {
+ err.frames = make([]StackFrame, len(err.stack))
+
+ for i, pc := range err.stack {
+ err.frames[i] = NewStackFrame(pc)
+ }
+ }
+
+ return err.frames
+}
+
+// TypeName returns the type this error. e.g. *errors.stringError.
+func (err *Error) TypeName() string {
+ if _, ok := err.Err.(uncaughtPanic); ok {
+ return "panic"
+ }
+ return reflect.TypeOf(err.Err).String()
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/errors/parse_panic.go b/vendor/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
new file mode 100644
index 0000000000..cc37052d78
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
@@ -0,0 +1,127 @@
+package errors
+
+import (
+ "strconv"
+ "strings"
+)
+
+type uncaughtPanic struct{ message string }
+
+func (p uncaughtPanic) Error() string {
+ return p.message
+}
+
+// ParsePanic allows you to get an error object from the output of a go program
+// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
+func ParsePanic(text string) (*Error, error) {
+ lines := strings.Split(text, "\n")
+
+ state := "start"
+
+ var message string
+ var stack []StackFrame
+
+ for i := 0; i < len(lines); i++ {
+ line := lines[i]
+
+ if state == "start" {
+ if strings.HasPrefix(line, "panic: ") {
+ message = strings.TrimPrefix(line, "panic: ")
+ state = "seek"
+ } else {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
+ }
+
+ } else if state == "seek" {
+ if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
+ state = "parsing"
+ }
+
+ } else if state == "parsing" {
+ if line == "" {
+ state = "done"
+ break
+ }
+ createdBy := false
+ if strings.HasPrefix(line, "created by ") {
+ line = strings.TrimPrefix(line, "created by ")
+ createdBy = true
+ }
+
+ i++
+
+ if i >= len(lines) {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
+ }
+
+ frame, err := parsePanicFrame(line, lines[i], createdBy)
+ if err != nil {
+ return nil, err
+ }
+
+ stack = append(stack, *frame)
+ if createdBy {
+ state = "done"
+ break
+ }
+ }
+ }
+
+ if state == "done" || state == "parsing" {
+ return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
+ }
+ return nil, Errorf("could not parse panic: %v", text)
+}
+
+// The lines we're passing look like this:
+//
+// main.(*foo).destruct(0xc208067e98)
+// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
+func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
+ idx := strings.LastIndex(name, "(")
+ if idx == -1 && !createdBy {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
+ }
+ if idx != -1 {
+ name = name[:idx]
+ }
+ pkg := ""
+
+ if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
+ pkg += name[:lastslash] + "/"
+ name = name[lastslash+1:]
+ }
+ if period := strings.Index(name, "."); period >= 0 {
+ pkg += name[:period]
+ name = name[period+1:]
+ }
+
+ name = strings.Replace(name, "·", ".", -1)
+
+ if !strings.HasPrefix(line, "\t") {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
+ }
+
+ idx = strings.LastIndex(line, ":")
+ if idx == -1 {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
+ }
+ file := line[1:idx]
+
+ number := line[idx+1:]
+ if idx = strings.Index(number, " +"); idx > -1 {
+ number = number[:idx]
+ }
+
+ lno, err := strconv.ParseInt(number, 10, 32)
+ if err != nil {
+ return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
+ }
+
+ return &StackFrame{
+ File: file,
+ LineNumber: int(lno),
+ Package: pkg,
+ Name: name,
+ }, nil
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/errors/stackframe.go b/vendor/github.com/bugsnag/bugsnag-go/errors/stackframe.go
new file mode 100644
index 0000000000..4edadbc589
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/errors/stackframe.go
@@ -0,0 +1,97 @@
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "runtime"
+ "strings"
+)
+
+// A StackFrame contains all necessary information about to generate a line
+// in a callstack.
+type StackFrame struct {
+ File string
+ LineNumber int
+ Name string
+ Package string
+ ProgramCounter uintptr
+}
+
+// NewStackFrame popoulates a stack frame object from the program counter.
+func NewStackFrame(pc uintptr) (frame StackFrame) {
+
+ frame = StackFrame{ProgramCounter: pc}
+ if frame.Func() == nil {
+ return
+ }
+ frame.Package, frame.Name = packageAndName(frame.Func())
+
+ // pc -1 because the program counters we use are usually return addresses,
+ // and we want to show the line that corresponds to the function call
+ frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
+ return
+
+}
+
+// Func returns the function that this stackframe corresponds to
+func (frame *StackFrame) Func() *runtime.Func {
+ if frame.ProgramCounter == 0 {
+ return nil
+ }
+ return runtime.FuncForPC(frame.ProgramCounter)
+}
+
+// String returns the stackframe formatted in the same way as go does
+// in runtime/debug.Stack()
+func (frame *StackFrame) String() string {
+ str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
+
+ source, err := frame.SourceLine()
+ if err != nil {
+ return str
+ }
+
+ return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
+}
+
+// SourceLine gets the line of code (from File and Line) of the original source if possible
+func (frame *StackFrame) SourceLine() (string, error) {
+ data, err := ioutil.ReadFile(frame.File)
+
+ if err != nil {
+ return "", err
+ }
+
+ lines := bytes.Split(data, []byte{'\n'})
+ if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
+ return "???", nil
+ }
+ // -1 because line-numbers are 1 based, but our array is 0 based
+ return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
+}
+
+func packageAndName(fn *runtime.Func) (string, string) {
+ name := fn.Name()
+ pkg := ""
+
+ // The name includes the path name to the package, which is unnecessary
+ // since the file name is already included. Plus, it has center dots.
+ // That is, we see
+ // runtime/debug.*T·ptrmethod
+ // and want
+ // *T.ptrmethod
+ // Since the package path might contains dots (e.g. code.google.com/...),
+ // we first remove the path prefix if there is one.
+ if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
+ pkg += name[:lastslash] + "/"
+ name = name[lastslash+1:]
+ }
+ if period := strings.Index(name, "."); period >= 0 {
+ pkg += name[:period]
+ name = name[period+1:]
+ }
+
+ name = strings.Replace(name, "·", ".", -1)
+ return pkg, name
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/event.go b/vendor/github.com/bugsnag/bugsnag-go/event.go
new file mode 100644
index 0000000000..320c6154c3
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/event.go
@@ -0,0 +1,143 @@
+package bugsnag
+
+import (
+ "strings"
+
+ "github.com/bugsnag/bugsnag-go/errors"
+)
+
+// Context is the context of the error in Bugsnag.
+// This can be passed to Notify, Recover or AutoNotify as rawData.
+type Context struct {
+ String string
+}
+
+// User represents the searchable user-data on Bugsnag. The Id is also used
+// to determine the number of users affected by a bug. This can be
+// passed to Notify, Recover or AutoNotify as rawData.
+type User struct {
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Email string `json:"email,omitempty"`
+}
+
+// ErrorClass overrides the error class in Bugsnag.
+// This struct enables you to group errors as you like.
+type ErrorClass struct {
+ Name string
+}
+
+// Sets the severity of the error on Bugsnag. These values can be
+// passed to Notify, Recover or AutoNotify as rawData.
+var (
+ SeverityError = severity{"error"}
+ SeverityWarning = severity{"warning"}
+ SeverityInfo = severity{"info"}
+)
+
+// The severity tag type, private so that people can only use Error,Warning,Info
+type severity struct {
+ String string
+}
+
+// The form of stacktrace that Bugsnag expects
+type stackFrame struct {
+ Method string `json:"method"`
+ File string `json:"file"`
+ LineNumber int `json:"lineNumber"`
+ InProject bool `json:"inProject,omitempty"`
+}
+
+// Event represents a payload of data that gets sent to Bugsnag.
+// This is passed to each OnBeforeNotify hook.
+type Event struct {
+
+ // The original error that caused this event, not sent to Bugsnag.
+ Error *errors.Error
+
+ // The rawData affecting this error, not sent to Bugsnag.
+ RawData []interface{}
+
+ // The error class to be sent to Bugsnag. This defaults to the type name of the Error, for
+ // example *error.String
+ ErrorClass string
+ // The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
+ Message string
+ // The stacktrrace of the error to be sent to Bugsnag.
+ Stacktrace []stackFrame
+
+ // The context to be sent to Bugsnag. This should be set to the part of the app that was running,
+ // e.g. for http requests, set it to the path.
+ Context string
+ // The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo.
+ Severity severity
+ // The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with
+ // the same grouping hash to group together in the dashboard.
+ GroupingHash string
+
+ // User data to send to Bugsnag. This is searchable on the dashboard.
+ User *User
+ // Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard.
+ MetaData MetaData
+}
+
+func newEvent(err *errors.Error, rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
+
+ config := notifier.Config
+ event := &Event{
+ Error: err,
+ RawData: append(notifier.RawData, rawData...),
+
+ ErrorClass: err.TypeName(),
+ Message: err.Error(),
+ Stacktrace: make([]stackFrame, len(err.StackFrames())),
+
+ Severity: SeverityWarning,
+
+ MetaData: make(MetaData),
+ }
+
+ for _, datum := range event.RawData {
+ switch datum := datum.(type) {
+ case severity:
+ event.Severity = datum
+
+ case Context:
+ event.Context = datum.String
+
+ case Configuration:
+ config = config.merge(&datum)
+
+ case MetaData:
+ event.MetaData.Update(datum)
+
+ case User:
+ event.User = &datum
+
+ case ErrorClass:
+ event.ErrorClass = datum.Name
+ }
+ }
+
+ for i, frame := range err.StackFrames() {
+ file := frame.File
+ inProject := config.isProjectPackage(frame.Package)
+
+ // remove $GOROOT and $GOHOME from other frames
+ if idx := strings.Index(file, frame.Package); idx > -1 {
+ file = file[idx:]
+ }
+ if inProject {
+ file = config.stripProjectPackages(file)
+ }
+
+ event.Stacktrace[i] = stackFrame{
+ Method: frame.Name,
+ File: file,
+ LineNumber: frame.LineNumber,
+ InProject: inProject,
+ }
+ }
+
+ return event, config
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/README.md b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/README.md
new file mode 100644
index 0000000000..1ac49148f5
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/README.md
@@ -0,0 +1,10 @@
+This is an example google app-engine app.
+
+To use it you will need to install the [App Engine
+SDK](https://cloud.google.com/appengine/downloads) for Go.
+
+Then run:
+
+ goapp deploy
+
+Then open: https://bugsnag-test.appspot.com/ in your web-browser.
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/app.yaml b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/app.yaml
new file mode 100644
index 0000000000..2fdff7b533
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/app.yaml
@@ -0,0 +1,8 @@
+application: bugsnag-test
+version: 1
+runtime: go
+api_version: go1
+
+handlers:
+- url: /.*
+ script: _go_app
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/hello.go b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/hello.go
new file mode 100644
index 0000000000..a5c8bbdd45
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/hello.go
@@ -0,0 +1,30 @@
+package mellow
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+
+ "github.com/bugsnag/bugsnag-go"
+)
+
+func init() {
+ bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
+ event.MetaData.AddStruct("original", event.Error.StackFrames())
+ return nil
+ })
+ bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "066f5ad3590596f9aa8d601ea89af845",
+ })
+
+ http.HandleFunc("/", bugsnag.HandlerFunc(handler))
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, "welcome")
+ notifier := bugsnag.New(r)
+ notifier.Notify(fmt.Errorf("oh hia"), bugsnag.MetaData{"env": {"values": os.Environ()}})
+ fmt.Fprint(w, "welcome\n")
+
+ panic("zoomg")
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/mylogs.txt b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/mylogs.txt
new file mode 100644
index 0000000000..a9c57d5f2e
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/appengine/mylogs.txt
@@ -0,0 +1,4 @@
+2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:16:25 -0700] "GET / HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
+2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:16:25 -0700] "GET /favicon.ico HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
+2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:18:20 -0700] "GET / HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
+2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:18:20 -0700] "GET /favicon.ico HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/http/main.go b/vendor/github.com/bugsnag/bugsnag-go/examples/http/main.go
new file mode 100644
index 0000000000..d32a76a64b
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/http/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/bugsnag/bugsnag-go"
+ "log"
+ "net/http"
+)
+
+func main() {
+
+ http.HandleFunc("/", Get)
+
+ bugsnag.Configure(bugsnag.Configuration{
+ APIKey: "066f5ad3590596f9aa8d601ea89af845",
+ })
+
+ log.Println("Serving on 9001")
+ http.ListenAndServe(":9001", bugsnag.Handler(nil))
+}
+
+func Get(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ w.Write([]byte("OK\n"))
+
+ var a struct{}
+ crash(a)
+}
+
+func crash(a interface{}) string {
+ return a.(string)
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/.gitignore b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/.gitignore
new file mode 100644
index 0000000000..dae67d0f7b
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/.gitignore
@@ -0,0 +1,3 @@
+test-results/
+tmp/
+routes/
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/controllers/app.go b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/controllers/app.go
new file mode 100644
index 0000000000..6cf8480888
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/controllers/app.go
@@ -0,0 +1,19 @@
+package controllers
+
+import "github.com/revel/revel"
+import "time"
+
+type App struct {
+ *revel.Controller
+}
+
+func (c App) Index() revel.Result {
+ go func() {
+ time.Sleep(5 * time.Second)
+ panic("hello!")
+ }()
+
+ s := make([]string, 0)
+ revel.INFO.Print(s[0])
+ return c.Render()
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/init.go b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/init.go
new file mode 100644
index 0000000000..5795414a1c
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/init.go
@@ -0,0 +1,40 @@
+package app
+
+import "github.com/revel/revel"
+import "github.com/bugsnag/bugsnag-go/revel"
+
+func init() {
+ // Filters is the default set of global filters.
+ revel.Filters = []revel.Filter{
+ revel.PanicFilter, // Recover from panics and display an error page instead.
+ bugsnagrevel.Filter, // Send panics to Bugsnag
+ revel.RouterFilter, // Use the routing table to select the right Action
+ revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
+ revel.ParamsFilter, // Parse parameters into Controller.Params.
+ revel.SessionFilter, // Restore and write the session cookie.
+ revel.FlashFilter, // Restore and write the flash cookie.
+ revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
+ revel.I18nFilter, // Resolve the requested language
+ HeaderFilter, // Add some security based headers
+ revel.InterceptorFilter, // Run interceptors around the action.
+ revel.CompressFilter, // Compress the result.
+ revel.ActionInvoker, // Invoke the action.
+ }
+
+ // register startup functions with OnAppStart
+ // ( order dependent )
+ // revel.OnAppStart(InitDB())
+ // revel.OnAppStart(FillCache())
+}
+
+// TODO turn this into revel.HeaderFilter
+// should probably also have a filter for CSRF
+// not sure if it can go in the same filter or not
+var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
+ // Add some common security headers
+ c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
+ c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
+ c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
+
+ fc[0](c, fc[1:]) // Execute the next filter stage.
+}
diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/App/Index.html b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/App/Index.html
new file mode 100644
index 0000000000..deb2304c8c
--- /dev/null
+++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/App/Index.html
@@ -0,0 +1,23 @@
+{{set . "title" "Home"}}
+{{template "header.html" .}}
+
+It works!
+
+
+ {{.Description}} +
+ {{end}} +{{end}} + + diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/500.html b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/500.html new file mode 100644 index 0000000000..0cef4deb4a --- /dev/null +++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/500.html @@ -0,0 +1,16 @@ + + + ++ This exception has been logged. +
+ {{end}} + + diff --git a/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/flash.html b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/flash.html new file mode 100644 index 0000000000..9c9ade9692 --- /dev/null +++ b/vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/flash.html @@ -0,0 +1,18 @@ +{{if .flash.success}} +