github.com/echohead/hub@v2.2.1+incompatible/github/crash_report.go (about) 1 package github 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "reflect" 9 "runtime" 10 "strings" 11 12 "github.com/github/hub/git" 13 "github.com/github/hub/ui" 14 "github.com/github/hub/utils" 15 ) 16 17 const ( 18 hubReportCrashConfig = "hub.reportCrash" 19 hubProjectOwner = "github" 20 hubProjectName = "hub" 21 ) 22 23 func CaptureCrash() { 24 if rec := recover(); rec != nil { 25 if err, ok := rec.(error); ok { 26 reportCrash(err) 27 } else if err, ok := rec.(string); ok { 28 reportCrash(errors.New(err)) 29 } 30 } 31 } 32 33 func reportCrash(err error) { 34 if err == nil { 35 return 36 } 37 38 buf := make([]byte, 10000) 39 runtime.Stack(buf, false) 40 stack := formatStack(buf) 41 42 switch reportCrashConfig() { 43 case "always": 44 report(err, stack) 45 case "never": 46 printError(err, stack) 47 default: 48 printError(err, stack) 49 fmt.Print("Would you like to open an issue? ([Y]es/[N]o/[A]lways/N[e]ver): ") 50 var confirm string 51 fmt.Scan(&confirm) 52 53 always := utils.IsOption(confirm, "a", "always") 54 if always || utils.IsOption(confirm, "y", "yes") { 55 report(err, stack) 56 } 57 58 saveReportConfiguration(confirm, always) 59 } 60 os.Exit(1) 61 } 62 63 func report(reportedError error, stack string) { 64 title, body, err := reportTitleAndBody(reportedError, stack) 65 utils.Check(err) 66 67 project := NewProject(hubProjectOwner, hubProjectName, GitHubHost) 68 69 gh := NewClient(project.Host) 70 71 issue, err := gh.CreateIssue(project, title, body, []string{"Crash Report"}) 72 utils.Check(err) 73 74 ui.Println(issue.HTMLURL) 75 } 76 77 func reportTitleAndBody(reportedError error, stack string) (title, body string, err error) { 78 message := "Crash report - %v\n\nError (%s): `%v`\n\nStack:\n\n```\n%s\n```\n\nRuntime:\n\n```\n%s\n```\n\n" 79 message += ` 80 # Creating crash report: 81 # 82 # This information will be posted as a new issue under jingweno/gh. 83 # We're NOT including any information about the command that you were executing, 84 # but knowing a little bit more about it would really help us to solve this problem. 85 # Feel free to modify the title and the description for this issue. 86 ` 87 88 errType := reflect.TypeOf(reportedError).String() 89 message = fmt.Sprintf(message, reportedError, errType, reportedError, stack, runtimeInfo()) 90 91 editor, err := NewEditor("CRASH_REPORT", "crash report", message) 92 if err != nil { 93 return "", "", err 94 } 95 96 defer editor.DeleteFile() 97 98 return editor.EditTitleAndBody() 99 } 100 101 func runtimeInfo() string { 102 return fmt.Sprintf("GOOS: %s\nGOARCH: %s", runtime.GOOS, runtime.GOARCH) 103 } 104 105 func formatStack(buf []byte) string { 106 buf = bytes.Trim(buf, "\x00") 107 108 stack := strings.Split(string(buf), "\n") 109 stack = append(stack[0:1], stack[5:]...) 110 111 return strings.Join(stack, "\n") 112 } 113 114 func printError(err error, stack string) { 115 ui.Printf("%v\n\n", err) 116 ui.Println(stack) 117 } 118 119 func saveReportConfiguration(confirm string, always bool) { 120 if always { 121 git.SetGlobalConfig(hubReportCrashConfig, "always") 122 } else if utils.IsOption(confirm, "e", "never") { 123 git.SetGlobalConfig(hubReportCrashConfig, "never") 124 } 125 } 126 127 func reportCrashConfig() (opt string) { 128 opt = os.Getenv("HUB_REPORT_CRASH") 129 if opt == "" { 130 opt, _ = git.GlobalConfig(hubReportCrashConfig) 131 } 132 133 return 134 }