fix(core): Deduplicate native HTTP breadcrumbs#6132
Conversation
…fetch breadcrumbs Closes #3045 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d52d0ef. Configure here.
Network errors, aborted requests, and CORS failures produce breadcrumbs without a status_code. Treat both sides being null/undefined as a match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously only string timestamps were parsed; numeric timestamps (seconds since epoch) were silently dropped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Nice fix! |
| } | ||
|
|
||
| const sameMethod = nativeBreadcrumb.data?.method === jsBreadcrumb.data?.method; | ||
| const sameUrl = nativeBreadcrumb.data?.url === jsBreadcrumb.data?.url; |
There was a problem hiding this comment.
What do you think of using something like endsWith here? Usually the start of the backend url will always match, only the end/center will vary. That could skip checking the initial URL.
There was a problem hiding this comment.
Although if almost all the time this function is called is a match, then it's a bit pointless to check for it
There was a problem hiding this comment.
Yes, and having a looser check with endsWith might risk false positives
|
Question: Can't we simply disable the auto capture for http request on the native side? |
@lucas-zimerman Good question 👍 I considered it as an approach (option 1 here) and I agree that is is a cleaner architecturally. In reality disabling didn't work since we don't know if a request came from JS or from native code. The solution is flagging the JS requests (e.g. extra flag, see option 3) and filtering out on the native side resulting in a more complicated solution. Approach 4 seemed simpler to implement but I'm open to discuss the alternatives. |
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d038a14+dirty | 3845.71 ms | 1228.11 ms | -2617.59 ms |
| 5569641+dirty | 3839.22 ms | 1231.30 ms | -2607.91 ms |
| 5a21b51+dirty | 3823.11 ms | 1214.46 ms | -2608.65 ms |
| 4b87b12+dirty | 1212.90 ms | 1222.09 ms | 9.19 ms |
| f3215d3+dirty | 3842.73 ms | 1219.33 ms | -2623.40 ms |
| 7d6fd3a+dirty | 1223.29 ms | 1229.57 ms | 6.28 ms |
| 3d377b5+dirty | 1218.48 ms | 1219.51 ms | 1.03 ms |
| 23598c3+dirty | 1207.00 ms | 1209.90 ms | 2.90 ms |
| 4953e94+dirty | 1212.06 ms | 1214.83 ms | 2.77 ms |
| 04207c4+dirty | 1191.27 ms | 1189.78 ms | -1.48 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d038a14+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 5569641+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 5a21b51+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 4b87b12+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| 7d6fd3a+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| 3d377b5+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
| 23598c3+dirty | 3.38 MiB | 4.80 MiB | 1.42 MiB |
| 4953e94+dirty | 3.38 MiB | 4.73 MiB | 1.35 MiB |
| 04207c4+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
📲 Install BuildsAndroid
|
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 3d377b5+dirty | 406.18 ms | 453.52 ms | 47.34 ms |
| 8929511+dirty | 405.33 ms | 452.16 ms | 46.83 ms |
| 100ce80+dirty | 463.66 ms | 539.56 ms | 75.90 ms |
| 3817909+dirty | 406.67 ms | 416.58 ms | 9.91 ms |
| 3b6e9f9+dirty | 442.70 ms | 486.44 ms | 43.74 ms |
| 4e0ba9c+dirty | 452.84 ms | 473.36 ms | 20.52 ms |
| 5fe1c6c+dirty | 401.62 ms | 445.28 ms | 43.66 ms |
| 44c8b3f+dirty | 414.20 ms | 457.28 ms | 43.08 ms |
| 890d145+dirty | 504.54 ms | 491.55 ms | -12.99 ms |
| 3ce5254+dirty | 410.57 ms | 448.48 ms | 37.91 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 3d377b5+dirty | 43.75 MiB | 48.14 MiB | 4.39 MiB |
| 8929511+dirty | 43.75 MiB | 48.16 MiB | 4.41 MiB |
| 100ce80+dirty | 48.30 MiB | 53.46 MiB | 5.15 MiB |
| 3817909+dirty | 43.75 MiB | 48.08 MiB | 4.33 MiB |
| 3b6e9f9+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| 4e0ba9c+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 5fe1c6c+dirty | 43.75 MiB | 48.14 MiB | 4.39 MiB |
| 44c8b3f+dirty | 48.30 MiB | 53.46 MiB | 5.15 MiB |
| 890d145+dirty | 43.75 MiB | 48.14 MiB | 4.39 MiB |
| 3ce5254+dirty | 43.75 MiB | 48.12 MiB | 4.37 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d038a14+dirty | 3831.11 ms | 1216.30 ms | -2614.81 ms |
| 5569641+dirty | 3824.35 ms | 1210.78 ms | -2613.57 ms |
| 5a21b51+dirty | 3837.87 ms | 1223.47 ms | -2614.40 ms |
| 4b87b12+dirty | 1199.49 ms | 1199.78 ms | 0.29 ms |
| f3215d3+dirty | 3846.08 ms | 1231.85 ms | -2614.23 ms |
| 7d6fd3a+dirty | 1210.89 ms | 1217.63 ms | 6.74 ms |
| 3d377b5+dirty | 1201.55 ms | 1201.80 ms | 0.25 ms |
| 23598c3+dirty | 1223.59 ms | 1229.13 ms | 5.53 ms |
| 4953e94+dirty | 1217.41 ms | 1223.53 ms | 6.12 ms |
| 04207c4+dirty | 1228.55 ms | 1226.04 ms | -2.51 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d038a14+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 5569641+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 5a21b51+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 4b87b12+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| 7d6fd3a+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| 3d377b5+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
| 23598c3+dirty | 3.38 MiB | 4.80 MiB | 1.42 MiB |
| 4953e94+dirty | 3.38 MiB | 4.73 MiB | 1.35 MiB |
| 04207c4+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a736b76+dirty | 405.78 ms | 458.74 ms | 52.96 ms |
| 890d145+dirty | 486.42 ms | 514.85 ms | 28.43 ms |
| 5fe1c6c+dirty | 365.84 ms | 408.62 ms | 42.78 ms |
| 44c8b3f+dirty | 492.13 ms | 563.47 ms | 71.34 ms |
| 04207c4+dirty | 395.40 ms | 456.55 ms | 61.15 ms |
| 3b6e9f9+dirty | 442.39 ms | 486.44 ms | 44.05 ms |
| 4e0ba9c+dirty | 421.39 ms | 455.80 ms | 34.41 ms |
| 3817909+dirty | 357.52 ms | 391.52 ms | 34.00 ms |
| bc0d8cf+dirty | 407.66 ms | 461.35 ms | 53.69 ms |
| 5569641+dirty | 465.92 ms | 532.22 ms | 66.30 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a736b76+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 890d145+dirty | 43.94 MiB | 49.00 MiB | 5.06 MiB |
| 5fe1c6c+dirty | 43.94 MiB | 49.00 MiB | 5.06 MiB |
| 44c8b3f+dirty | 48.30 MiB | 53.46 MiB | 5.15 MiB |
| 04207c4+dirty | 43.94 MiB | 48.98 MiB | 5.04 MiB |
| 3b6e9f9+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| 4e0ba9c+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 3817909+dirty | 43.94 MiB | 48.94 MiB | 5.00 MiB |
| bc0d8cf+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 5569641+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
📢 Type of change
📜 Description
When JS makes an HTTP request in React Native, breadcrumbs are created independently by both the JS SDK (
xhrcategory) and the native SDK (httpcategory), because RN's networking layer (fetch/XHR) is implemented via native APIs (NSURLSessionon iOS,OkHttpon Android). This results in duplicate breadcrumbs for every HTTP request.This PR adds deduplication logic in
deviceContextIntegration.processEventthat filters out nativehttpbreadcrumbs when a matching JSxhr/fetchbreadcrumb exists (same method, URL, status code, and timestamp within 2s tolerance).Key design choices:
beforeBreadcrumb, giving users filtering controlstatus_codemismatch — usesNumber()coercion for comparisonAlso updates the FIXME comment in
breadcrumbs.tsto reflect that deduplication now happens indeviceContextIntegration.💡 Motivation and Context
Closes #3045
Users see duplicate HTTP breadcrumbs (2x per request) which crowd out meaningful breadcrumbs and cannot be filtered via
beforeBreadcrumbsince native breadcrumbs bypass that callback.The sentry-cocoa side of this (cocoa producing its own internal duplicates, #2971) was fixed in sentry-cocoa 8.8.0. The remaining duplication is the JS vs native layer, which this PR addresses.
💚 How did you test it?
yarn build)yarn lint)📝 Checklist
sendDefaultPIIis enabled🔮 Next steps