integrations.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. package sentry
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "regexp"
  9. "runtime"
  10. "strings"
  11. )
  12. // ================================
  13. // Modules Integration
  14. // ================================
  15. type modulesIntegration struct{}
  16. var _modulesCache map[string]string // nolint: gochecknoglobals
  17. func (mi *modulesIntegration) Name() string {
  18. return "Modules"
  19. }
  20. func (mi *modulesIntegration) SetupOnce(client *Client) {
  21. client.AddEventProcessor(mi.processor)
  22. }
  23. func (mi *modulesIntegration) processor(event *Event, hint *EventHint) *Event {
  24. if event.Modules == nil {
  25. event.Modules = extractModules()
  26. }
  27. return event
  28. }
  29. func extractModules() map[string]string {
  30. if _modulesCache != nil {
  31. return _modulesCache
  32. }
  33. extractedModules, err := getModules()
  34. if err != nil {
  35. Logger.Printf("ModuleIntegration wasn't able to extract modules: %v\n", err)
  36. return nil
  37. }
  38. _modulesCache = extractedModules
  39. return extractedModules
  40. }
  41. func getModules() (map[string]string, error) {
  42. if fileExists("go.mod") {
  43. return getModulesFromMod()
  44. }
  45. if fileExists("vendor") {
  46. // Priority given to vendor created by modules
  47. if fileExists("vendor/modules.txt") {
  48. return getModulesFromVendorTxt()
  49. }
  50. if fileExists("vendor/vendor.json") {
  51. return getModulesFromVendorJSON()
  52. }
  53. }
  54. return nil, fmt.Errorf("module integration failed")
  55. }
  56. func getModulesFromMod() (map[string]string, error) {
  57. modules := make(map[string]string)
  58. file, err := os.Open("go.mod")
  59. if err != nil {
  60. return nil, fmt.Errorf("unable to open mod file")
  61. }
  62. defer file.Close()
  63. areModulesPresent := false
  64. scanner := bufio.NewScanner(file)
  65. for scanner.Scan() {
  66. splits := strings.Split(scanner.Text(), " ")
  67. if splits[0] == "require" {
  68. areModulesPresent = true
  69. // Mod file has only 1 dependency
  70. if len(splits) > 2 {
  71. modules[strings.TrimSpace(splits[1])] = splits[2]
  72. return modules, nil
  73. }
  74. } else if areModulesPresent && splits[0] != ")" {
  75. modules[strings.TrimSpace(splits[0])] = splits[1]
  76. }
  77. }
  78. if scannerErr := scanner.Err(); scannerErr != nil {
  79. return nil, scannerErr
  80. }
  81. return modules, nil
  82. }
  83. func getModulesFromVendorTxt() (map[string]string, error) {
  84. modules := make(map[string]string)
  85. file, err := os.Open("vendor/modules.txt")
  86. if err != nil {
  87. return nil, fmt.Errorf("unable to open vendor/modules.txt")
  88. }
  89. defer file.Close()
  90. scanner := bufio.NewScanner(file)
  91. for scanner.Scan() {
  92. splits := strings.Split(scanner.Text(), " ")
  93. if splits[0] == "#" {
  94. modules[splits[1]] = splits[2]
  95. }
  96. }
  97. if scannerErr := scanner.Err(); scannerErr != nil {
  98. return nil, scannerErr
  99. }
  100. return modules, nil
  101. }
  102. func getModulesFromVendorJSON() (map[string]string, error) {
  103. modules := make(map[string]string)
  104. file, err := ioutil.ReadFile("vendor/vendor.json")
  105. if err != nil {
  106. return nil, fmt.Errorf("unable to open vendor/vendor.json")
  107. }
  108. var vendor map[string]interface{}
  109. if unmarshalErr := json.Unmarshal(file, &vendor); unmarshalErr != nil {
  110. return nil, unmarshalErr
  111. }
  112. packages := vendor["package"].([]interface{})
  113. // To avoid iterative dependencies, TODO: Change of default value
  114. lastPath := "\n"
  115. for _, value := range packages {
  116. path := value.(map[string]interface{})["path"].(string)
  117. if !strings.Contains(path, lastPath) {
  118. // No versions are available through vendor.json
  119. modules[path] = ""
  120. lastPath = path
  121. }
  122. }
  123. return modules, nil
  124. }
  125. // ================================
  126. // Environment Integration
  127. // ================================
  128. type environmentIntegration struct{}
  129. func (ei *environmentIntegration) Name() string {
  130. return "Environment"
  131. }
  132. func (ei *environmentIntegration) SetupOnce(client *Client) {
  133. client.AddEventProcessor(ei.processor)
  134. }
  135. func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Event {
  136. if event.Contexts == nil {
  137. event.Contexts = make(map[string]interface{})
  138. }
  139. event.Contexts["device"] = map[string]interface{}{
  140. "arch": runtime.GOARCH,
  141. "num_cpu": runtime.NumCPU(),
  142. }
  143. event.Contexts["os"] = map[string]interface{}{
  144. "name": runtime.GOOS,
  145. }
  146. event.Contexts["runtime"] = map[string]interface{}{
  147. "name": "go",
  148. "version": runtime.Version(),
  149. }
  150. return event
  151. }
  152. // ================================
  153. // Ignore Errors Integration
  154. // ================================
  155. type ignoreErrorsIntegration struct {
  156. ignoreErrors []*regexp.Regexp
  157. }
  158. func (iei *ignoreErrorsIntegration) Name() string {
  159. return "IgnoreErrors"
  160. }
  161. func (iei *ignoreErrorsIntegration) SetupOnce(client *Client) {
  162. iei.ignoreErrors = transformStringsIntoRegexps(client.Options().IgnoreErrors)
  163. client.AddEventProcessor(iei.processor)
  164. }
  165. func (iei *ignoreErrorsIntegration) processor(event *Event, hint *EventHint) *Event {
  166. suspects := getIgnoreErrorsSuspects(event)
  167. for _, suspect := range suspects {
  168. for _, pattern := range iei.ignoreErrors {
  169. if pattern.Match([]byte(suspect)) {
  170. Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
  171. "| Value matched: %s | Filter used: %s", suspect, pattern)
  172. return nil
  173. }
  174. }
  175. }
  176. return event
  177. }
  178. func transformStringsIntoRegexps(strings []string) []*regexp.Regexp {
  179. var exprs []*regexp.Regexp
  180. for _, s := range strings {
  181. r, err := regexp.Compile(s)
  182. if err == nil {
  183. exprs = append(exprs, r)
  184. }
  185. }
  186. return exprs
  187. }
  188. func getIgnoreErrorsSuspects(event *Event) []string {
  189. suspects := []string{}
  190. if event.Message != "" {
  191. suspects = append(suspects, event.Message)
  192. }
  193. for _, ex := range event.Exception {
  194. suspects = append(suspects, ex.Type)
  195. suspects = append(suspects, ex.Value)
  196. }
  197. return suspects
  198. }
  199. // ================================
  200. // Contextify Frames Integration
  201. // ================================
  202. type contextifyFramesIntegration struct {
  203. sr sourceReader
  204. contextLines int
  205. cachedLocations map[string]string
  206. }
  207. func (cfi *contextifyFramesIntegration) Name() string {
  208. return "ContextifyFrames"
  209. }
  210. func (cfi *contextifyFramesIntegration) SetupOnce(client *Client) {
  211. cfi.sr = newSourceReader()
  212. cfi.contextLines = 5
  213. cfi.cachedLocations = make(map[string]string)
  214. client.AddEventProcessor(cfi.processor)
  215. }
  216. func (cfi *contextifyFramesIntegration) processor(event *Event, hint *EventHint) *Event {
  217. // Range over all exceptions
  218. for _, ex := range event.Exception {
  219. // If it has no stacktrace, just bail out
  220. if ex.Stacktrace == nil {
  221. continue
  222. }
  223. // If it does, it should have frames, so try to contextify them
  224. ex.Stacktrace.Frames = cfi.contextify(ex.Stacktrace.Frames)
  225. }
  226. // Range over all threads
  227. for _, th := range event.Threads {
  228. // If it has no stacktrace, just bail out
  229. if th.Stacktrace == nil {
  230. continue
  231. }
  232. // If it does, it should have frames, so try to contextify them
  233. th.Stacktrace.Frames = cfi.contextify(th.Stacktrace.Frames)
  234. }
  235. return event
  236. }
  237. func (cfi *contextifyFramesIntegration) contextify(frames []Frame) []Frame {
  238. contextifiedFrames := make([]Frame, 0, len(frames))
  239. for _, frame := range frames {
  240. if !frame.InApp {
  241. contextifiedFrames = append(contextifiedFrames, frame)
  242. continue
  243. }
  244. var path string
  245. if cachedPath, ok := cfi.cachedLocations[frame.AbsPath]; ok {
  246. path = cachedPath
  247. } else {
  248. // Optimize for happy path here
  249. if fileExists(frame.AbsPath) {
  250. path = frame.AbsPath
  251. } else {
  252. path = cfi.findNearbySourceCodeLocation(frame.AbsPath)
  253. }
  254. }
  255. if path == "" {
  256. contextifiedFrames = append(contextifiedFrames, frame)
  257. continue
  258. }
  259. lines, contextLine := cfi.sr.readContextLines(path, frame.Lineno, cfi.contextLines)
  260. contextifiedFrames = append(contextifiedFrames, cfi.addContextLinesToFrame(frame, lines, contextLine))
  261. }
  262. return contextifiedFrames
  263. }
  264. func (cfi *contextifyFramesIntegration) findNearbySourceCodeLocation(originalPath string) string {
  265. trimmedPath := strings.TrimPrefix(originalPath, "/")
  266. components := strings.Split(trimmedPath, "/")
  267. for len(components) > 0 {
  268. components = components[1:]
  269. possibleLocation := strings.Join(components, "/")
  270. if fileExists(possibleLocation) {
  271. cfi.cachedLocations[originalPath] = possibleLocation
  272. return possibleLocation
  273. }
  274. }
  275. cfi.cachedLocations[originalPath] = ""
  276. return ""
  277. }
  278. func (cfi *contextifyFramesIntegration) addContextLinesToFrame(frame Frame, lines [][]byte, contextLine int) Frame {
  279. for i, line := range lines {
  280. switch {
  281. case i < contextLine:
  282. frame.PreContext = append(frame.PreContext, string(line))
  283. case i == contextLine:
  284. frame.ContextLine = string(line)
  285. default:
  286. frame.PostContext = append(frame.PostContext, string(line))
  287. }
  288. }
  289. return frame
  290. }