package sentry import ( "fmt" "regexp" "runtime" "runtime/debug" "strings" "sync" ) // ================================ // Modules Integration // ================================ type modulesIntegration struct { once sync.Once modules map[string]string } 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 len(event.Modules) == 0 { mi.once.Do(func() { info, ok := debug.ReadBuildInfo() if !ok { Logger.Print("The Modules integration is not available in binaries built without module support.") return } mi.modules = extractModules(info) }) } event.Modules = mi.modules return event } func extractModules(info *debug.BuildInfo) map[string]string { modules := map[string]string{ info.Main.Path: info.Main.Version, } for _, dep := range info.Deps { ver := dep.Version if dep.Replace != nil { ver += fmt.Sprintf(" => %s %s", dep.Replace.Path, dep.Replace.Version) } modules[dep.Path] = strings.TrimSuffix(ver, " ") } return modules } // ================================ // 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 { // Initialize maps as necessary. contextNames := []string{"device", "os", "runtime"} if event.Contexts == nil { event.Contexts = make(map[string]Context, len(contextNames)) } for _, name := range contextNames { if event.Contexts[name] == nil { event.Contexts[name] = make(Context) } } // Set contextual information preserving existing data. For each context, if // the existing value is not of type map[string]interface{}, then no // additional information is added. if deviceContext, ok := event.Contexts["device"]; ok { if _, ok := deviceContext["arch"]; !ok { deviceContext["arch"] = runtime.GOARCH } if _, ok := deviceContext["num_cpu"]; !ok { deviceContext["num_cpu"] = runtime.NumCPU() } } if osContext, ok := event.Contexts["os"]; ok { if _, ok := osContext["name"]; !ok { osContext["name"] = runtime.GOOS } } if runtimeContext, ok := event.Contexts["runtime"]; ok { if _, ok := runtimeContext["name"]; !ok { runtimeContext["name"] = "go" } if _, ok := runtimeContext["version"]; !ok { runtimeContext["version"] = runtime.Version() } if _, ok := runtimeContext["go_numroutines"]; !ok { runtimeContext["go_numroutines"] = runtime.NumGoroutine() } if _, ok := runtimeContext["go_maxprocs"]; !ok { runtimeContext["go_maxprocs"] = runtime.GOMAXPROCS(0) } if _, ok := runtimeContext["go_numcgocalls"]; !ok { runtimeContext["go_numcgocalls"] = runtime.NumCgoCall() } } 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, ex.Value) } return suspects } // ================================ // Contextify Frames Integration // ================================ type contextifyFramesIntegration struct { sr sourceReader contextLines int cachedLocations sync.Map } func (cfi *contextifyFramesIntegration) Name() string { return "ContextifyFrames" } func (cfi *contextifyFramesIntegration) SetupOnce(client *Client) { cfi.sr = newSourceReader() cfi.contextLines = 5 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.Load(frame.AbsPath); ok { if p, ok := cachedPath.(string); ok { path = p } } 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.Store(originalPath, possibleLocation) return possibleLocation } } cfi.cachedLocations.Store(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 }