parse_panic.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package errors
  2. import (
  3. "strconv"
  4. "strings"
  5. )
  6. type uncaughtPanic struct{ message string }
  7. func (p uncaughtPanic) Error() string {
  8. return p.message
  9. }
  10. // ParsePanic allows you to get an error object from the output of a go program
  11. // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
  12. func ParsePanic(text string) (*Error, error) {
  13. lines := strings.Split(text, "\n")
  14. state := "start"
  15. var message string
  16. var stack []StackFrame
  17. for i := 0; i < len(lines); i++ {
  18. line := lines[i]
  19. if state == "start" {
  20. if strings.HasPrefix(line, "panic: ") {
  21. message = strings.TrimPrefix(line, "panic: ")
  22. state = "seek"
  23. } else {
  24. return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
  25. }
  26. } else if state == "seek" {
  27. if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
  28. state = "parsing"
  29. }
  30. } else if state == "parsing" {
  31. if line == "" {
  32. state = "done"
  33. break
  34. }
  35. createdBy := false
  36. if strings.HasPrefix(line, "created by ") {
  37. line = strings.TrimPrefix(line, "created by ")
  38. createdBy = true
  39. }
  40. i++
  41. if i >= len(lines) {
  42. return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
  43. }
  44. frame, err := parsePanicFrame(line, lines[i], createdBy)
  45. if err != nil {
  46. return nil, err
  47. }
  48. stack = append(stack, *frame)
  49. if createdBy {
  50. state = "done"
  51. break
  52. }
  53. }
  54. }
  55. if state == "done" || state == "parsing" {
  56. return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
  57. }
  58. return nil, Errorf("could not parse panic: %v", text)
  59. }
  60. // The lines we're passing look like this:
  61. //
  62. // main.(*foo).destruct(0xc208067e98)
  63. // /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
  64. func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
  65. idx := strings.LastIndex(name, "(")
  66. if idx == -1 && !createdBy {
  67. return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
  68. }
  69. if idx != -1 {
  70. name = name[:idx]
  71. }
  72. pkg := ""
  73. if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
  74. pkg += name[:lastslash] + "/"
  75. name = name[lastslash+1:]
  76. }
  77. if period := strings.Index(name, "."); period >= 0 {
  78. pkg += name[:period]
  79. name = name[period+1:]
  80. }
  81. name = strings.Replace(name, "·", ".", -1)
  82. if !strings.HasPrefix(line, "\t") {
  83. return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
  84. }
  85. idx = strings.LastIndex(line, ":")
  86. if idx == -1 {
  87. return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
  88. }
  89. file := line[1:idx]
  90. number := line[idx+1:]
  91. if idx = strings.Index(number, " +"); idx > -1 {
  92. number = number[:idx]
  93. }
  94. lno, err := strconv.ParseInt(number, 10, 32)
  95. if err != nil {
  96. return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
  97. }
  98. return &StackFrame{
  99. File: file,
  100. LineNumber: int(lno),
  101. Package: pkg,
  102. Name: name,
  103. }, nil
  104. }