enrichSourceContext walks every stacktrace in a Sentry error payload and attaches source code to frames that reference a fetchable URL but carry none. It returns the (possibly rewritten) payload and whether anything changed.
(payload json.RawMessage, fetch sourceFetcher)
| 68 | // attaches source code to frames that reference a fetchable URL but carry none. |
| 69 | // It returns the (possibly rewritten) payload and whether anything changed. |
| 70 | func enrichSourceContext(payload json.RawMessage, fetch sourceFetcher) (json.RawMessage, bool) { |
| 71 | if fetch == nil { |
| 72 | fetch = httpSourceFetcher |
| 73 | } |
| 74 | |
| 75 | var root map[string]any |
| 76 | if err := json.Unmarshal(payload, &root); err != nil { |
| 77 | return payload, false |
| 78 | } |
| 79 | |
| 80 | cache := map[string]*fetchedSource{} // url -> parsed file (per-event, dedupes refetches) |
| 81 | missing := map[string]bool{} // url -> fetch already failed |
| 82 | changed := false |
| 83 | |
| 84 | visit := func(st any) { |
| 85 | if enrichStacktrace(st, fetch, cache, missing) { |
| 86 | changed = true |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | if exc, ok := root["exception"].(map[string]any); ok { |
| 91 | for _, v := range asAnySlice(exc["values"]) { |
| 92 | if vm, ok := v.(map[string]any); ok { |
| 93 | visit(vm["stacktrace"]) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | if th, ok := root["threads"].(map[string]any); ok { |
| 98 | for _, v := range asAnySlice(th["values"]) { |
| 99 | if vm, ok := v.(map[string]any); ok { |
| 100 | visit(vm["stacktrace"]) |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | visit(root["stacktrace"]) |
| 105 | |
| 106 | if !changed { |
| 107 | return payload, false |
| 108 | } |
| 109 | |
| 110 | out, err := json.Marshal(root) |
| 111 | if err != nil { |
| 112 | return payload, false |
| 113 | } |
| 114 | return out, true |
| 115 | } |
| 116 | |
| 117 | func asAnySlice(v any) []any { |
| 118 | s, _ := v.([]any) |