package main import ( "fmt" "html/template" "log" "os" "os/exec" "path/filepath" "runtime" "runtime/debug" "strings" "time" "github.com/robert-nix/ansihtml" ) // https://gist.github.com/swdunlop/9629168 func identifyPanic() string { var name, file string var line int var pc [16]uintptr n := runtime.Callers(4, pc[:]) for _, pc := range pc[:n] { fn := runtime.FuncForPC(pc) if fn == nil { continue } file, line = fn.FileLine(pc) name = fn.Name() if !strings.HasPrefix(name, "runtime.") { break } } switch { case name != "": return fmt.Sprintf("%v:%v", name, line) case file != "": return fmt.Sprintf("%v:%v", file, line) } return fmt.Sprintf("pc:%x", pc) } // OpenFile attempts to open a given file in the appropriate GUI application. func OpenFile(fpath string) (err error) { switch PLATFORM { case "linux": err = exec.Command("xdg-open", fpath).Start() case "windows": err = exec.Command("rundll32", "url.dll,FileProtocolHandler", fpath).Start() case "darwin": err = exec.Command("open", fpath).Start() default: err = fmt.Errorf("unknown os") } return } // Exit dumps the last 100 lines of output to a crash file in /tmp (or equivalent), and generates a prettier HTML file containing it that is opened in the browser if possible. func Exit(err interface{}) { tmpl, err2 := template.ParseFS(localFS, "html/crash.html", "html/header.html") if err2 != nil { log.Fatalf("Failed to load template: %v", err) } logCache := lineCache.String() if err != nil { fmt.Println(err) logCache += "\n" + fmt.Sprint(err) } logCache += "\n" + string(debug.Stack()) sanitized := sanitizeLog(logCache) data := map[string]interface{}{ "Log": logCache, "SanitizedLog": sanitized, } if err != nil { data["Err"] = fmt.Sprintf("%s %v", identifyPanic(), err) } // Use dashes for time rather than colons for Windows fpath := filepath.Join(temp, "jfa-go-crash-"+time.Now().Local().Format("2006-01-02T15-04-05")) err2 = os.WriteFile(fpath+".txt", []byte(logCache), 0666) if err2 != nil { log.Fatalf("Failed to write crash dump file: %v", err2) } log.Printf("\n------\nA crash report has been saved to \"%s\".\n------", fpath+".txt") // Render ANSI colors to HTML data["Log"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["Log"].(string))))) data["SanitizedLog"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["SanitizedLog"].(string))))) data["Err"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["Err"].(string))))) f, err2 := os.OpenFile(fpath+".html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err2 != nil { log.Fatalf("Failed to open crash dump file: %v", err2) } defer f.Close() err2 = tmpl.Execute(f, data) if err2 != nil { log.Fatalf("Failed to execute template: %v", err2) } if err := OpenFile(fpath + ".html"); err != nil { log.Printf("Failed to open browser, trying text file...") OpenFile(fpath + ".txt") } if TRAY { QuitTray() } else { os.Exit(1) } }