From dcd26bffe67792799a981ddd08cd98fb70402a4e Mon Sep 17 00:00:00 2001 From: Yashish Dua Date: Tue, 14 May 2019 01:24:01 +0530 Subject: [PATCH] feat: Module Integration Implementation --- client.go | 4 - examples/basic/main.go | 1 + integrations/modules.go | 166 ++++++++++++++++++++++++++++++++++++++++ integrations/util.go | 17 ++++ util.go | 4 + 5 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 integrations/modules.go create mode 100644 integrations/util.go diff --git a/client.go b/client.go index 8567721c4..a09a5dd59 100644 --- a/client.go +++ b/client.go @@ -4,8 +4,6 @@ import ( "context" "crypto/x509" "io" - "io/ioutil" - "log" "math/rand" "net/http" "os" @@ -13,8 +11,6 @@ import ( "time" ) -var debugger = log.New(ioutil.Discard, "[Sentry]", log.LstdFlags) - type Integration interface { Name() string SetupOnce() diff --git a/examples/basic/main.go b/examples/basic/main.go index e435a55b8..c46750f24 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -143,6 +143,7 @@ func main() { Transport: new(DevNullTransport), Integrations: []sentry.Integration{ new(sentryintegrations.EnvironmentIntegration), + new(sentryintegrations.ModulesIntegration), }, }); err != nil { panic(err) diff --git a/integrations/modules.go b/integrations/modules.go new file mode 100644 index 000000000..6369f0e2f --- /dev/null +++ b/integrations/modules.go @@ -0,0 +1,166 @@ +package sentryintegrations + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/getsentry/sentry-go" +) + +type ModulesIntegration struct{} + +var _modulesCache map[string]string + +func (mi ModulesIntegration) Name() string { + return "Modules" +} + +func (mi ModulesIntegration) SetupOnce() { + sentry.AddGlobalEventProcessor(mi.processor) +} + +func (mi ModulesIntegration) processor(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + // Run the integration only on the Client that registered it + if sentry.CurrentHub().GetIntegration(mi.Name()) == nil { + return 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 { + debugger.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 +} diff --git a/integrations/util.go b/integrations/util.go new file mode 100644 index 000000000..3d137d10e --- /dev/null +++ b/integrations/util.go @@ -0,0 +1,17 @@ +package sentryintegrations + +import ( + "io/ioutil" + "log" + "os" +) + +var debugger = log.New(ioutil.Discard, "[Sentry]", log.LstdFlags) + +func fileExists(fileName string) bool { + if _, err := os.Stat(fileName); err != nil { + return false + } + + return true +} diff --git a/util.go b/util.go index 073ca60ec..d1cadd4fb 100644 --- a/util.go +++ b/util.go @@ -6,8 +6,12 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" + "log" ) +var debugger = log.New(ioutil.Discard, "[Sentry]", log.LstdFlags) + func uuid() string { id := make([]byte, 16) _, _ = io.ReadFull(rand.Reader, id)