| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package sentry
- import (
- "bufio"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "regexp"
- "runtime"
- "strings"
- )
- // ================================
- // Modules Integration
- // ================================
- type modulesIntegration struct{}
- var _modulesCache map[string]string // nolint: gochecknoglobals
- func (mi *modulesIntegration) Name() string {
- return "Modules"
- }
- func (mi *modulesIntegration) SetupOnce(client *Client) {
- client.AddEventProcessor(mi.processor)
- }
- func (mi *modulesIntegration) processor(event *Event, hint *EventHint) *Event {
- if event.Modules == nil {
- event.Modules = extractModules()
- }
- return event
- }
- func extractModules() map[string]string {
- if _modulesCache != nil {
- return _modulesCache
- }
- extractedModules, err := getModules()
- if err != nil {
- Logger.Printf("ModuleIntegration wasn't able to extract modules: %v\n", err)
- return nil
- }
- _modulesCache = extractedModules
- return extractedModules
- }
- func getModules() (map[string]string, error) {
- if fileExists("go.mod") {
- return getModulesFromMod()
- }
- if fileExists("vendor") {
- // Priority given to vendor created by modules
- if fileExists("vendor/modules.txt") {
- return getModulesFromVendorTxt()
- }
- if fileExists("vendor/vendor.json") {
- return getModulesFromVendorJSON()
- }
- }
- return nil, fmt.Errorf("module integration failed")
- }
- func getModulesFromMod() (map[string]string, error) {
- modules := make(map[string]string)
- file, err := os.Open("go.mod")
- if err != nil {
- return nil, fmt.Errorf("unable to open mod file")
- }
- defer file.Close()
- areModulesPresent := false
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- splits := strings.Split(scanner.Text(), " ")
- if splits[0] == "require" {
- areModulesPresent = true
- // Mod file has only 1 dependency
- if len(splits) > 2 {
- modules[strings.TrimSpace(splits[1])] = splits[2]
- return modules, nil
- }
- } else if areModulesPresent && splits[0] != ")" {
- modules[strings.TrimSpace(splits[0])] = splits[1]
- }
- }
- if scannerErr := scanner.Err(); scannerErr != nil {
- return nil, scannerErr
- }
- return modules, nil
- }
- func getModulesFromVendorTxt() (map[string]string, error) {
- modules := make(map[string]string)
- file, err := os.Open("vendor/modules.txt")
- if err != nil {
- return nil, fmt.Errorf("unable to open vendor/modules.txt")
- }
- defer file.Close()
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- splits := strings.Split(scanner.Text(), " ")
- if splits[0] == "#" {
- modules[splits[1]] = splits[2]
- }
- }
- if scannerErr := scanner.Err(); scannerErr != nil {
- return nil, scannerErr
- }
- return modules, nil
- }
- func getModulesFromVendorJSON() (map[string]string, error) {
- modules := make(map[string]string)
- file, err := ioutil.ReadFile("vendor/vendor.json")
- if err != nil {
- return nil, fmt.Errorf("unable to open vendor/vendor.json")
- }
- var vendor map[string]interface{}
- if unmarshalErr := json.Unmarshal(file, &vendor); unmarshalErr != nil {
- return nil, unmarshalErr
- }
- packages := vendor["package"].([]interface{})
- // To avoid iterative dependencies, TODO: Change of default value
- lastPath := "\n"
- for _, value := range packages {
- path := value.(map[string]interface{})["path"].(string)
- if !strings.Contains(path, lastPath) {
- // No versions are available through vendor.json
- modules[path] = ""
- lastPath = path
- }
- }
- return modules, nil
- }
- // ================================
- // Environment Integration
- // ================================
- type environmentIntegration struct{}
- func (ei *environmentIntegration) Name() string {
- return "Environment"
- }
- func (ei *environmentIntegration) SetupOnce(client *Client) {
- client.AddEventProcessor(ei.processor)
- }
- func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Event {
- if event.Contexts == nil {
- event.Contexts = make(map[string]interface{})
- }
- event.Contexts["device"] = map[string]interface{}{
- "arch": runtime.GOARCH,
- "num_cpu": runtime.NumCPU(),
- }
- event.Contexts["os"] = map[string]interface{}{
- "name": runtime.GOOS,
- }
- event.Contexts["runtime"] = map[string]interface{}{
- "name": "go",
- "version": runtime.Version(),
- }
- return event
- }
- // ================================
- // Ignore Errors Integration
- // ================================
- type ignoreErrorsIntegration struct {
- ignoreErrors []*regexp.Regexp
- }
- func (iei *ignoreErrorsIntegration) Name() string {
- return "IgnoreErrors"
- }
- func (iei *ignoreErrorsIntegration) SetupOnce(client *Client) {
- iei.ignoreErrors = transformStringsIntoRegexps(client.Options().IgnoreErrors)
- client.AddEventProcessor(iei.processor)
- }
- func (iei *ignoreErrorsIntegration) processor(event *Event, hint *EventHint) *Event {
- suspects := getIgnoreErrorsSuspects(event)
- for _, suspect := range suspects {
- for _, pattern := range iei.ignoreErrors {
- if pattern.Match([]byte(suspect)) {
- Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
- "| Value matched: %s | Filter used: %s", suspect, pattern)
- return nil
- }
- }
- }
- return event
- }
- func transformStringsIntoRegexps(strings []string) []*regexp.Regexp {
- var exprs []*regexp.Regexp
- for _, s := range strings {
- r, err := regexp.Compile(s)
- if err == nil {
- exprs = append(exprs, r)
- }
- }
- return exprs
- }
- func getIgnoreErrorsSuspects(event *Event) []string {
- suspects := []string{}
- if event.Message != "" {
- suspects = append(suspects, event.Message)
- }
- for _, ex := range event.Exception {
- suspects = append(suspects, ex.Type)
- suspects = append(suspects, ex.Value)
- }
- return suspects
- }
- // ================================
- // Contextify Frames Integration
- // ================================
- type contextifyFramesIntegration struct {
- sr sourceReader
- contextLines int
- cachedLocations map[string]string
- }
- func (cfi *contextifyFramesIntegration) Name() string {
- return "ContextifyFrames"
- }
- func (cfi *contextifyFramesIntegration) SetupOnce(client *Client) {
- cfi.sr = newSourceReader()
- cfi.contextLines = 5
- cfi.cachedLocations = make(map[string]string)
- client.AddEventProcessor(cfi.processor)
- }
- func (cfi *contextifyFramesIntegration) processor(event *Event, hint *EventHint) *Event {
- // Range over all exceptions
- for _, ex := range event.Exception {
- // If it has no stacktrace, just bail out
- if ex.Stacktrace == nil {
- continue
- }
- // If it does, it should have frames, so try to contextify them
- ex.Stacktrace.Frames = cfi.contextify(ex.Stacktrace.Frames)
- }
- // Range over all threads
- for _, th := range event.Threads {
- // If it has no stacktrace, just bail out
- if th.Stacktrace == nil {
- continue
- }
- // If it does, it should have frames, so try to contextify them
- th.Stacktrace.Frames = cfi.contextify(th.Stacktrace.Frames)
- }
- return event
- }
- func (cfi *contextifyFramesIntegration) contextify(frames []Frame) []Frame {
- contextifiedFrames := make([]Frame, 0, len(frames))
- for _, frame := range frames {
- if !frame.InApp {
- contextifiedFrames = append(contextifiedFrames, frame)
- continue
- }
- var path string
- if cachedPath, ok := cfi.cachedLocations[frame.AbsPath]; ok {
- path = cachedPath
- } else {
- // Optimize for happy path here
- if fileExists(frame.AbsPath) {
- path = frame.AbsPath
- } else {
- path = cfi.findNearbySourceCodeLocation(frame.AbsPath)
- }
- }
- if path == "" {
- contextifiedFrames = append(contextifiedFrames, frame)
- continue
- }
- lines, contextLine := cfi.sr.readContextLines(path, frame.Lineno, cfi.contextLines)
- contextifiedFrames = append(contextifiedFrames, cfi.addContextLinesToFrame(frame, lines, contextLine))
- }
- return contextifiedFrames
- }
- func (cfi *contextifyFramesIntegration) findNearbySourceCodeLocation(originalPath string) string {
- trimmedPath := strings.TrimPrefix(originalPath, "/")
- components := strings.Split(trimmedPath, "/")
- for len(components) > 0 {
- components = components[1:]
- possibleLocation := strings.Join(components, "/")
- if fileExists(possibleLocation) {
- cfi.cachedLocations[originalPath] = possibleLocation
- return possibleLocation
- }
- }
- cfi.cachedLocations[originalPath] = ""
- return ""
- }
- func (cfi *contextifyFramesIntegration) addContextLinesToFrame(frame Frame, lines [][]byte, contextLine int) Frame {
- for i, line := range lines {
- switch {
- case i < contextLine:
- frame.PreContext = append(frame.PreContext, string(line))
- case i == contextLine:
- frame.ContextLine = string(line)
- default:
- frame.PostContext = append(frame.PostContext, string(line))
- }
- }
- return frame
- }
|