sentry.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "errors"
  10. "io/ioutil"
  11. "regexp"
  12. "strings"
  13. "sync"
  14. raven "github.com/getsentry/raven-go"
  15. "github.com/maruel/panicparse/stack"
  16. )
  17. const reportServer = "https://crash.syncthing.net/report/"
  18. var loader = newGithubSourceCodeLoader()
  19. func init() {
  20. raven.SetSourceCodeLoader(loader)
  21. }
  22. var (
  23. clients = make(map[string]*raven.Client)
  24. clientsMut sync.Mutex
  25. )
  26. func sendReport(dsn, path string, report []byte, userID string) error {
  27. pkt, err := parseReport(path, report)
  28. if err != nil {
  29. return err
  30. }
  31. pkt.Interfaces = append(pkt.Interfaces, &raven.User{ID: userID})
  32. clientsMut.Lock()
  33. defer clientsMut.Unlock()
  34. cli, ok := clients[dsn]
  35. if !ok {
  36. cli, err = raven.New(dsn)
  37. if err != nil {
  38. return err
  39. }
  40. clients[dsn] = cli
  41. }
  42. // The client sets release and such on the packet before sending, in the
  43. // misguided idea that it knows this better than than the packet we give
  44. // it. So we copy the values from the packet to the client first...
  45. cli.SetRelease(pkt.Release)
  46. cli.SetEnvironment(pkt.Environment)
  47. defer cli.Wait()
  48. _, errC := cli.Capture(pkt, nil)
  49. return <-errC
  50. }
  51. func parseReport(path string, report []byte) (*raven.Packet, error) {
  52. parts := bytes.SplitN(report, []byte("\n"), 2)
  53. if len(parts) != 2 {
  54. return nil, errors.New("no first line")
  55. }
  56. version, err := parseVersion(string(parts[0]))
  57. if err != nil {
  58. return nil, err
  59. }
  60. report = parts[1]
  61. foundPanic := false
  62. var subjectLine []byte
  63. for {
  64. parts = bytes.SplitN(report, []byte("\n"), 2)
  65. if len(parts) != 2 {
  66. return nil, errors.New("no panic line found")
  67. }
  68. line := parts[0]
  69. report = parts[1]
  70. if foundPanic {
  71. // The previous line was our "Panic at ..." header. We are now
  72. // at the beginning of the real panic trace and this is our
  73. // subject line.
  74. subjectLine = line
  75. break
  76. } else if bytes.HasPrefix(line, []byte("Panic at")) {
  77. foundPanic = true
  78. }
  79. }
  80. r := bytes.NewReader(report)
  81. ctx, err := stack.ParseDump(r, ioutil.Discard, false)
  82. if err != nil {
  83. return nil, err
  84. }
  85. // Lock the source code loader to the version we are processing here.
  86. if version.commit != "" {
  87. // We have a commit hash, so we know exactly which source to use
  88. loader.LockWithVersion(version.commit)
  89. } else if strings.HasPrefix(version.tag, "v") {
  90. // Lets hope the tag is close enough
  91. loader.LockWithVersion(version.tag)
  92. } else {
  93. // Last resort
  94. loader.LockWithVersion("master")
  95. }
  96. defer loader.Unlock()
  97. var trace raven.Stacktrace
  98. for _, gr := range ctx.Goroutines {
  99. if gr.First {
  100. trace.Frames = make([]*raven.StacktraceFrame, len(gr.Stack.Calls))
  101. for i, sc := range gr.Stack.Calls {
  102. trace.Frames[len(trace.Frames)-1-i] = raven.NewStacktraceFrame(0, sc.Func.Name(), sc.SrcPath, sc.Line, 3, nil)
  103. }
  104. break
  105. }
  106. }
  107. pkt := &raven.Packet{
  108. Message: string(subjectLine),
  109. Platform: "go",
  110. Release: version.tag,
  111. Environment: version.environment(),
  112. Tags: raven.Tags{
  113. raven.Tag{Key: "version", Value: version.version},
  114. raven.Tag{Key: "tag", Value: version.tag},
  115. raven.Tag{Key: "codename", Value: version.codename},
  116. raven.Tag{Key: "runtime", Value: version.runtime},
  117. raven.Tag{Key: "goos", Value: version.goos},
  118. raven.Tag{Key: "goarch", Value: version.goarch},
  119. raven.Tag{Key: "builder", Value: version.builder},
  120. },
  121. Extra: raven.Extra{
  122. "url": reportServer + path,
  123. },
  124. Interfaces: []raven.Interface{&trace},
  125. }
  126. if version.commit != "" {
  127. pkt.Tags = append(pkt.Tags, raven.Tag{Key: "commit", Value: version.commit})
  128. }
  129. return pkt, nil
  130. }
  131. // syncthing v1.1.4-rc.1+30-g6aaae618-dirty-crashrep "Erbium Earthworm" (go1.12.5 darwin-amd64) [email protected] 2019-05-23 16:08:14 UTC
  132. var longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)`)
  133. type version struct {
  134. version string // "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep"
  135. tag string // "v1.1.4-rc.1"
  136. commit string // "6aaae618", blank when absent
  137. codename string // "Erbium Earthworm"
  138. runtime string // "go1.12.5"
  139. goos string // "darwin"
  140. goarch string // "amd64"
  141. builder string // "[email protected]"
  142. }
  143. func (v version) environment() string {
  144. if v.commit != "" {
  145. return "Development"
  146. }
  147. if strings.Contains(v.tag, "-rc.") {
  148. return "Candidate"
  149. }
  150. if strings.Contains(v.tag, "-") {
  151. return "Beta"
  152. }
  153. return "Stable"
  154. }
  155. func parseVersion(line string) (version, error) {
  156. m := longVersionRE.FindStringSubmatch(line)
  157. if len(m) == 0 {
  158. return version{}, errors.New("unintelligeble version string")
  159. }
  160. v := version{
  161. version: m[1],
  162. codename: m[2],
  163. runtime: m[3],
  164. goos: m[4],
  165. goarch: m[5],
  166. builder: m[6],
  167. }
  168. parts := strings.Split(v.version, "+")
  169. v.tag = parts[0]
  170. if len(parts) > 1 {
  171. fields := strings.Split(parts[1], "-")
  172. if len(fields) >= 2 && strings.HasPrefix(fields[1], "g") {
  173. v.commit = fields[1][1:]
  174. }
  175. }
  176. return v, nil
  177. }