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