| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- package errors
- import (
- "strconv"
- "strings"
- )
- type uncaughtPanic struct{ message string }
- func (p uncaughtPanic) Error() string {
- return p.message
- }
- // ParsePanic allows you to get an error object from the output of a go program
- // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
- func ParsePanic(text string) (*Error, error) {
- lines := strings.Split(text, "\n")
- state := "start"
- var message string
- var stack []StackFrame
- for i := 0; i < len(lines); i++ {
- line := lines[i]
- if state == "start" {
- if strings.HasPrefix(line, "panic: ") {
- message = strings.TrimPrefix(line, "panic: ")
- state = "seek"
- } else {
- return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
- }
- } else if state == "seek" {
- if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
- state = "parsing"
- }
- } else if state == "parsing" {
- if line == "" {
- state = "done"
- break
- }
- createdBy := false
- if strings.HasPrefix(line, "created by ") {
- line = strings.TrimPrefix(line, "created by ")
- createdBy = true
- }
- i++
- if i >= len(lines) {
- return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
- }
- frame, err := parsePanicFrame(line, lines[i], createdBy)
- if err != nil {
- return nil, err
- }
- stack = append(stack, *frame)
- if createdBy {
- state = "done"
- break
- }
- }
- }
- if state == "done" || state == "parsing" {
- return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
- }
- return nil, Errorf("could not parse panic: %v", text)
- }
- // The lines we're passing look like this:
- //
- // main.(*foo).destruct(0xc208067e98)
- // /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
- func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
- idx := strings.LastIndex(name, "(")
- if idx == -1 && !createdBy {
- return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
- }
- if idx != -1 {
- name = name[:idx]
- }
- pkg := ""
- if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
- pkg += name[:lastslash] + "/"
- name = name[lastslash+1:]
- }
- if period := strings.Index(name, "."); period >= 0 {
- pkg += name[:period]
- name = name[period+1:]
- }
- name = strings.Replace(name, "·", ".", -1)
- if !strings.HasPrefix(line, "\t") {
- return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
- }
- idx = strings.LastIndex(line, ":")
- if idx == -1 {
- return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
- }
- file := line[1:idx]
- number := line[idx+1:]
- if idx = strings.Index(number, " +"); idx > -1 {
- number = number[:idx]
- }
- lno, err := strconv.ParseInt(number, 10, 32)
- if err != nil {
- return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
- }
- return &StackFrame{
- File: file,
- LineNumber: int(lno),
- Package: pkg,
- Name: name,
- }, nil
- }
|