forked from docker/machine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
crash_report.go
149 lines (122 loc) · 3.43 KB
/
crash_report.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package crashreport
import (
"fmt"
"os"
"runtime"
"bytes"
"os/exec"
"path/filepath"
"errors"
"io/ioutil"
"github.com/bugsnag/bugsnag-go"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/shell"
"github.com/docker/machine/version"
)
const (
defaultAPIKey = "a9697f9a010c33ee218a65e5b1f3b0c1"
noreportAPIKey = "no-report"
)
type CrashReporter interface {
Send(err CrashError) error
}
// CrashError describes an error that should be reported to bugsnag
type CrashError struct {
Cause error
Command string
Context string
DriverName string
LogFilePath string
}
func (e CrashError) Error() string {
return e.Cause.Error()
}
type BugsnagCrashReporter struct {
baseDir string
apiKey string
}
// NewCrashReporter creates a new bugsnag based CrashReporter. Needs an apiKey.
var NewCrashReporter = func(baseDir string, apiKey string) CrashReporter {
if apiKey == "" {
apiKey = defaultAPIKey
}
return &BugsnagCrashReporter{
baseDir: baseDir,
apiKey: apiKey,
}
}
// Send sends a crash report to bugsnag via an http call.
func (r *BugsnagCrashReporter) Send(err CrashError) error {
if r.noReportFileExist() || r.apiKey == noreportAPIKey {
log.Debug("Opting out of crash reporting.")
return nil
}
if r.apiKey == "" {
return errors.New("Not sending report since no api key has been set.")
}
bugsnag.Configure(bugsnag.Configuration{
APIKey: r.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)
detectOSVersion(&metaData)
addFile(err.LogFilePath, &metaData)
var buffer bytes.Buffer
for _, message := range log.History() {
buffer.WriteString(message + "\n")
}
metaData.Add("history", "trace", buffer.String())
return bugsnag.Notify(err.Cause, metaData, bugsnag.SeverityError, bugsnag.Context{String: err.Context}, bugsnag.ErrorClass{Name: fmt.Sprintf("%s/%s", err.DriverName, err.Command)})
}
func (r *BugsnagCrashReporter) noReportFileExist() bool {
optOutFilePath := filepath.Join(r.baseDir, "no-error-report")
if _, err := os.Stat(optOutFilePath); os.IsNotExist(err) {
return false
}
return true
}
func addFile(path string, metaData *bugsnag.MetaData) {
if path == "" {
return
}
file, err := os.Open(path)
if err != nil {
log.Debug(err)
return
}
data, err := ioutil.ReadAll(file)
if err != nil {
log.Debug(err)
return
}
metaData.Add("logfile", filepath.Base(path), string(data))
}
func detectRunningShell(metaData *bugsnag.MetaData) {
shell, err := shell.Detect()
if err == nil {
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))
}
func detectOSVersion(metaData *bugsnag.MetaData) {
metaData.Add("device", "os version", localOSVersion())
}