github.com/pengwynn/gh@v1.0.1-0.20140118055701-14327ca3942e/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  }