Skip to content

Conversation

@43jay
Copy link
Collaborator

@43jay 43jay commented Nov 19, 2025

📜 Description

Make network details capture (http request/response bodies and headers - docs) available to clients using the sentry gradle plugin by adding support to SentryOkHttpEventListener.

Updates CHANGELOG.

💡 Motivation and Context

Part of [Mobile Replay] Capture Network request/response bodies.

Network Details extraction depends on okhttp3.Interceptor (SentryOkHttpInterceptor) to intercept okhttp requests and extract the relevant data to the Breadcrumb Hint (see #4796).

However, the Sentry Interceptor is only responsible for sending Breadcrumb data if for some reason the SentryOkHttpEventListener is not used (code ref).

Given registering SentryOkHttpEventListener is the default gradle plugin behavior, typical SDK configuration will see Breadcrumb data sent via SentryOkHttpEventListener. So this PR modifies SentryOkHttpEvent to receive network details data from the interceptor, to be included when sending the Breadcrumb from that path.

💚 How did you test it?

Unit

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpEventTest*network*details*"
> SentryOkHttpEventTest > when setNetworkDetails is not called, no network details data is captured PASSED
> SentryOkHttpEventTest > when finish is called, the breadcrumb sent includes network details data on its hint PASSED
./gradlew :sentry:test --tests="*SentryReplayOptionsTest*Network*" --tests="*RRWebOptionsEventSerializationTest*network*" --tests="*NetworkDetailCaptureUtilsTest*"
> SentryReplayOptionsTest > getNetworkResponseHeaders returns default headers by default PASSED
> SentryReplayOptionsTest > setNetworkResponseHeaders adds to default headers PASSED
> SentryReplayOptionsTest > setNetworkRequestHeaders adds to default headers PASSED
> SentryReplayOptionsTest > getNetworkRequestHeaders returns default headers by default PASSED
> RRWebOptionsEventSerializationTest > networkDetailAllowUrls and headers are included when networkDetailAllowUrls is configured PASSED
> RRWebOptionsEventSerializationTest > networkCaptureBodies is included when networkDetailAllowUrls is configured PASSED
> RRWebOptionsEventSerializationTest > default networkCaptureBodies is included when networkDetailAllowUrls is configured PASSED
> RRWebOptionsEventSerializationTest > networkDetailDenyUrls are included when networkDetailAllowUrls is configured PASSED
> RRWebOptionsEventSerializationTest > default network request and response headers are included when networkDetailAllowUrls is configured but no custom headers set PASSED
> RRWebOptionsEventSerializationTest > network detail fields are not included when networkDetailAllowUrls is empty PASSED
> NetworkDetailCaptureUtilsTest > getCaptureHeaders should handle null allHeaders PASSED
> NetworkDetailCaptureUtilsTest > getCaptureHeaders should only capture allowed headers PASSED
> NetworkDetailCaptureUtilsTest > getCaptureHeaders should match headers case-insensitively PASSED
> NetworkDetailCaptureUtilsTest > getCaptureHeaders should handle null elements in allowedHeaders PASSED
> NetworkDetailCaptureUtilsTest > getCaptureHeaders should handle empty allowedHeaders PASSED
./gradlew :sentry-android-core:testDebugUnitTest --tests="*ManifestMetadataReaderTest*network*" --rerun-tasks

> ManifestMetadataReaderTest > applyMetadata keeps default networkCaptureBodies as true when not present PASSED
> ManifestMetadataReaderTest > applyMetadata trims whitespace from network URLs PASSED
> ManifestMetadataReaderTest > applyMetadata reads comma-separated networkDetailDenyUrls from manifest PASSED
> ManifestMetadataReaderTest > applyMetadata reads network events breadcrumbs and keep default value if not found PASSED
> ManifestMetadataReaderTest > applyMetadata skips empty strings for networkDetailAllowUrls and networkDetailDenyUrls PASSED
> ManifestMetadataReaderTest > applyMetadata reads comma-separated networkDetailAllowUrls from manifest PASSED
> ManifestMetadataReaderTest > applyMetadata reads network events breadcrumbs to options PASSED
> ManifestMetadataReaderTest > applyMetadata keeps the default networkRequestHeaders PASSED
> ManifestMetadataReaderTest > applyMetadata reads networkCaptureBodies from manifest PASSED
> ManifestMetadataReaderTest > applyMetadata reads networkResponseHeaders from manifest PASSED
> ManifestMetadataReaderTest > applyMetadata trims whitespace from network headers PASSED
> ManifestMetadataReaderTest > applyMetadata reads networkRequestHeaders from manifest PASSED
> ManifestMetadataReaderTest > applyMetadata keeps empty networkDetailDenyUrls when not present PASSED
> ManifestMetadataReaderTest > applyMetadata keeps the default networkResponseHeaders PASSED
> ManifestMetadataReaderTest > applyMetadata keeps empty networkDetailAllowUrls when not present PASSED
> ManifestMetadataReaderTest > applyMetadata skips empty strings for networkRequestHeaders and networkResponseHeaders PASSED

Manual

Build sentry-samples:
% ./gradlew :sentry-okhttp:clean :sentry-android-replay:clean :sentry-samples:sentry-samples-android:clean :sentry-samples:sentry-samples-android:installDebug --no-build-cache --rerun-tasks

1. OkHttpClient w/ Sentry Interceptor+EventListener will upload network details

Modify sentry-samples to create http requests with event listener & interceptor:
image

Verify session replay created as expected
https://sentry-sdks.sentry.io/explore/replays/09681100b32640b99f53fc68a016f00d/...

2. OKHttpClient w/ only Sentry Interceptor will upload network details

Comment out addEventListener line and rebuild/install:
image

Verify session replay created as expected
https://sentry-sdks.sentry.io/explore/replays/29ce82bbd31c482e9fb0a60122412d3d/...

3. OKHttpClient w/ NO Sentry Interceptor will NOT upload network details

Comment out addInterceptor line and rebuild/install:
image

Verify session replay created with no data as expected
https://sentry-sdks.sentry.io/explore/replays/1232a6f76a6e428281779e8021943429/...

📝 Checklist

  • I added tests to verify the changes. 2 new unit tests in SentryOkHttpEventTest
  • I added GH Issue ID & Linear ID
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled. N/A
  • No breaking change or entry added to the changelog. see CHANGELOG.md in this PR
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs. No sentry.api changes here
  • Review from the native team if needed. No native changes
  • Confirm that the gradle plugin always registers SentryOkHttpInterceptor confirmed

🔮 Next steps

@linear
Copy link

linear bot commented Nov 19, 2025

@43jay 43jay marked this pull request as draft November 19, 2025 19:31
@github-actions
Copy link
Contributor

github-actions bot commented Nov 19, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against a151c5b

@github-actions
Copy link
Contributor

github-actions bot commented Nov 19, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 312.11 ms 365.69 ms 53.58 ms
Size 1.58 MiB 2.13 MiB 557.45 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
604a261 380.65 ms 451.27 ms 70.62 ms
a416a65 295.53 ms 373.74 ms 78.21 ms
889ecea 367.58 ms 437.52 ms 69.94 ms
a5ab36f 320.47 ms 389.77 ms 69.30 ms
27d7cf8 397.90 ms 498.65 ms 100.75 ms
ee747ae 400.46 ms 423.61 ms 23.15 ms
d217708 375.27 ms 415.68 ms 40.41 ms
fcec2f2 357.47 ms 447.32 ms 89.85 ms
23d6b12 354.10 ms 408.38 ms 54.28 ms
d5a29b6 298.62 ms 391.78 ms 93.16 ms

App size

Revision Plain With Sentry Diff
604a261 1.58 MiB 2.10 MiB 533.42 KiB
a416a65 1.58 MiB 2.12 MiB 555.26 KiB
889ecea 1.58 MiB 2.11 MiB 539.75 KiB
a5ab36f 1.58 MiB 2.12 MiB 555.26 KiB
27d7cf8 1.58 MiB 2.12 MiB 549.42 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
d217708 1.58 MiB 2.10 MiB 532.97 KiB
fcec2f2 1.58 MiB 2.12 MiB 551.50 KiB
23d6b12 1.58 MiB 2.10 MiB 532.31 KiB
d5a29b6 1.58 MiB 2.12 MiB 549.37 KiB

Previous results on branch: 43jay/MOBILE-935

Startup times

Revision Plain With Sentry Diff
154fb27 336.98 ms 387.10 ms 50.12 ms
1a07f53 341.15 ms 378.86 ms 37.71 ms
1c4ba0c 302.22 ms 367.21 ms 64.98 ms
7b2c5a6 341.45 ms 406.52 ms 65.07 ms
fedc731 325.81 ms 383.02 ms 57.21 ms
6687052 321.98 ms 383.35 ms 61.38 ms
3162f1d 308.61 ms 367.50 ms 58.89 ms
55850ea 370.79 ms 431.33 ms 60.55 ms
9e1feff 261.67 ms 369.02 ms 107.35 ms
da92356 319.12 ms 388.73 ms 69.61 ms

App size

Revision Plain With Sentry Diff
154fb27 1.58 MiB 2.13 MiB 557.32 KiB
1a07f53 1.58 MiB 2.12 MiB 553.02 KiB
1c4ba0c 1.58 MiB 2.13 MiB 556.34 KiB
7b2c5a6 1.58 MiB 2.13 MiB 557.32 KiB
fedc731 1.58 MiB 2.13 MiB 557.33 KiB
6687052 1.58 MiB 2.13 MiB 556.25 KiB
3162f1d 1.58 MiB 2.13 MiB 557.32 KiB
55850ea 1.58 MiB 2.13 MiB 557.31 KiB
9e1feff 1.58 MiB 2.13 MiB 557.34 KiB
da92356 1.58 MiB 2.13 MiB 556.24 KiB

@43jay 43jay marked this pull request as ready for review November 19, 2025 20:09
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Duplicate HTTP headers lost in conversion

The toMap() function loses duplicate HTTP headers with the same name. When iterating through OkHttp headers and inserting into a Map<String, String>, duplicate header names get overwritten, keeping only the last value. This causes data loss for headers like Set-Cookie, Accept, or Vary that commonly appear multiple times in HTTP messages. The captured network details will be incomplete when responses or requests contain duplicate headers.

sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt#L265-L272

/** Extracts headers from OkHttp Headers object into a map */
private fun okhttp3.Headers.toMap(): Map<String, String> {
val headers = linkedMapOf<String, String>()
for (i in 0 until size) {
headers[name(i)] = value(i)
}
return headers
}

Fix in Cursor Fix in Web


@43jay 43jay changed the title [android] Capture network request/response details when using SentryOkHttpListener feat(replay): Capture network request/response details when using SentryOkHttpListener Nov 21, 2025
43jay added a commit that referenced this pull request Nov 21, 2025
review comment - #4919 (review)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
@43jay
Copy link
Collaborator Author

43jay commented Nov 21, 2025

Bug: Duplicate HTTP headers lost in conversion

The toMap() function loses duplicate HTTP headers with the same name. When iterating through OkHttp headers and inserting into a Map<String, String>, duplicate header names get overwritten, keeping only the last value. This causes data loss for headers like Set-Cookie, Accept, or Vary that commonly appear multiple times in HTTP messages. The captured network details will be incomplete when responses or requests contain duplicate headers.

fixed

43jay added a commit that referenced this pull request Nov 21, 2025
…ator

Issue is that commas are valid separators in certain headers (Cookie, Set-Cookie,...).
Switch to semi-colon separated instead -> this only governs the formatted list that appears in the sentry dashboard so is relatively minor.

review comment - #4919 (comment)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
Copy link
Member

@markushi markushi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, I've left a few comments

43jay added a commit that referenced this pull request Nov 25, 2025
review comment - #4919 (review)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
43jay added a commit that referenced this pull request Nov 25, 2025
…ator

Issue is that commas are valid separators in certain headers (Cookie, Set-Cookie,...).
Switch to semi-colon separated instead -> this only governs the formatted list that appears in the sentry dashboard so is relatively minor.

review comment - #4919 (comment)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
@43jay 43jay force-pushed the 43jay/MOBILE-935 branch 7 times, most recently from d075ef2 to 6d104b8 Compare November 25, 2025 15:57
@43jay 43jay force-pushed the 43jay/MOBILE-935 branch 7 times, most recently from 56f8ea0 to 2d60638 Compare November 25, 2025 16:25
@43jay
Copy link
Collaborator Author

43jay commented Nov 25, 2025

lots of force pushes to get the markdown formatted correctly 🫠 for whatever reason android studio markdown viewer was playing up...

Copy link
Member

@romtsn romtsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, as discussed internally let's change the options to accept lists instead of arrays

43jay and others added 16 commits November 26, 2025 10:45
Run tests
./gradlew :sentry:test --tests="*NetworkDetailCaptureUtilsTest*"
Reuse existing logic that retrieves optional SentryOkHttpEvent for the okhttp3.Call, and optionally provide NetworkRequestData for adding to Breadcrumb Hint in SentryOkHttpEvent#finish
./gradlew :sentry-okhttp:test --tests="*SentryOkHttpEventTest*setNetworkDetails*"
review comment - https://github.com/getsentry/sentry-java/pull/4919/files#r2550496731

seems possible, e.g. if a client passes null in the array to SentryReplayOptions#set[Request|Response]Headers
review comment - #4919 (review)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
…ator

Issue is that commas are valid separators in certain headers (Cookie, Set-Cookie,...).
Switch to semi-colon separated instead -> this only governs the formatted list that appears in the sentry dashboard so is relatively minor.

review comment - #4919 (comment)

./gradlew :sentry-okhttp:test --tests="*SentryOkHttpInterceptorTest.toMap handles duplicate headers correctly*"
Previously some api was expecting String[], others were using List<String>. Change everything to List<String> for consistency
@43jay 43jay enabled auto-merge (squash) November 26, 2025 14:57
@43jay 43jay merged commit 4742e23 into main Nov 26, 2025
61 checks passed
@43jay 43jay deleted the 43jay/MOBILE-935 branch November 26, 2025 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants