-
-
Notifications
You must be signed in to change notification settings - Fork 467
feat(replay): Adding OkHttp Request/Response bodies #4796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
b8555e2
ca28040
5397e9d
71ed70e
f2ce22e
201102a
724ec42
2d08e7b
ebc5ff3
122a8a6
6964a53
25c42c7
308072b
5836ef1
d9f8254
6b6f3fe
cadc529
465b6e5
6ab8e03
d3f1a24
9eba521
8deffe9
669c8a3
8b29cdd
4bc5b77
177ae0c
a204a40
6629e94
ef79439
13c1da8
4937d69
c2e4477
ad8e402
584df60
9545155
8c9333b
bcb4efa
38b8919
b887de7
adb0482
a575db7
74c8c6b
85cda10
3fc8376
0784c51
9a80627
37a6b27
9b09be4
2ddca3b
10a52b4
d749c9a
2e6cf74
ec9985f
71c1f93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ import io.sentry.util.PropagationTargetsUtils | |
| import io.sentry.util.SpanUtils | ||
| import io.sentry.util.TracingUtils | ||
| import io.sentry.util.UrlUtils | ||
| import io.sentry.util.network.NetworkRequestData | ||
|
43jay marked this conversation as resolved.
|
||
| import io.sentry.util.network.ReplayNetworkRequestOrResponse | ||
| import java.io.IOException | ||
| import okhttp3.Interceptor | ||
| import okhttp3.Request | ||
|
|
@@ -172,18 +174,30 @@ public open class SentryOkHttpInterceptor( | |
| startTimestamp: Long, | ||
| ) { | ||
| val breadcrumb = Breadcrumb.http(request.url.toString(), request.method, code) | ||
|
|
||
| // Track request and response body sizes for the breadcrumb | ||
| var requestBodySize: Long? = null | ||
| var responseBodySize: Long? = null | ||
|
|
||
| request.body?.contentLength().ifHasValidLength { | ||
| breadcrumb.setData("http.request_content_length", it) | ||
| requestBodySize = it | ||
| } | ||
|
|
||
| val hint = Hint().also { it.set(OKHTTP_REQUEST, request) } | ||
| response?.let { | ||
| it.body?.contentLength().ifHasValidLength { responseBodySize -> | ||
| breadcrumb.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, responseBodySize) | ||
| } | ||
| response?.body?.contentLength().ifHasValidLength { | ||
| breadcrumb.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, it) | ||
| responseBodySize = it | ||
| } | ||
|
|
||
| hint[OKHTTP_RESPONSE] = it | ||
| val hint = Hint().also { | ||
| // Set the structured network data for replay | ||
| val networkData = createNetworkRequestData(request, response, requestBodySize, responseBodySize) | ||
| it.set("replay:networkDetails", networkData) | ||
|
|
||
| // it.set(OKHTTP_REQUEST, request) | ||
|
43jay marked this conversation as resolved.
Outdated
|
||
| // response?.let { resp -> it[OKHTTP_RESPONSE] = resp } | ||
| } | ||
|
|
||
| // needs this as unix timestamp for rrweb | ||
| breadcrumb.setData(SpanDataConvention.HTTP_START_TIMESTAMP, startTimestamp) | ||
| breadcrumb.setData( | ||
|
|
@@ -194,6 +208,116 @@ public open class SentryOkHttpInterceptor( | |
| scopes.addBreadcrumb(breadcrumb, hint) | ||
| } | ||
|
|
||
| /** | ||
| * Extracts headers from OkHttp Headers object into a map | ||
| */ | ||
| private fun okhttp3.Headers.toMap(): Map<String, String> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wondering if we can make this potentially faster with less allocations, e.g.: private fun okhttp3.Headers.toMap(): Map<String, String> {
val headers = LinkedHashMap<String, String>(size)
for (i in 0 until size) {
headers[name(i)] = value(i)
}
return headers
}
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still pending
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| val headers = mutableMapOf<String, String>() | ||
| for (name in names()) { | ||
| headers[name] = get(name) ?: "" | ||
| } | ||
| return headers | ||
| } | ||
|
|
||
| /** | ||
| * Extracts body metadata from OkHttp RequestBody or ResponseBody | ||
| * Note: We don't consume the actual body stream to avoid interfering with the request/response | ||
|
43jay marked this conversation as resolved.
Outdated
|
||
| */ | ||
| private fun extractBodyMetadata( | ||
| contentLength: Long?, | ||
| contentType: okhttp3.MediaType? | ||
| ): Pair<Long?, Any?> { | ||
| val bodySize = contentLength?.takeIf { it >= 0 } | ||
| val bodyInfo = if (contentLength != null && contentLength != 0L) { | ||
| mapOf( | ||
| "contentType" to contentType?.toString(), | ||
| "hasBody" to true | ||
| ) | ||
| } else null | ||
|
|
||
| return bodySize to bodyInfo | ||
| } | ||
|
|
||
| /** | ||
| * Creates a NetworkRequestData object from the request and response | ||
| */ | ||
| private fun createNetworkRequestData( | ||
| request: Request, | ||
| response: Response?, | ||
| requestBodySize: Long?, | ||
| responseBodySize: Long? | ||
| ): NetworkRequestData { | ||
| // Log the incoming request details | ||
| println("SentryNetwork: Creating NetworkRequestData for: ${request.method} ${request.url}") | ||
| scopes.options.logger.log( | ||
| io.sentry.SentryLevel.INFO, | ||
| "SentryNetwork: Creating NetworkRequestData for: ${request.method} ${request.url}" | ||
| ) | ||
|
|
||
| // Extract request data | ||
| val requestHeaders = request.headers.toMap() | ||
| val (reqBodySize, reqBodyInfo) = extractBodyMetadata( | ||
| request.body?.contentLength(), | ||
| request.body?.contentType() | ||
| ) | ||
|
|
||
| scopes.options.logger.log( | ||
| io.sentry.SentryLevel.INFO, | ||
| "SentryNetwork: Request - Headers count: ${requestHeaders.size}, Body size: $reqBodySize, Body info: $reqBodyInfo" | ||
| ) | ||
|
43jay marked this conversation as resolved.
Outdated
|
||
|
|
||
| val requestData = ReplayNetworkRequestOrResponse( | ||
| reqBodySize, | ||
| reqBodyInfo, | ||
| requestHeaders | ||
| ) | ||
|
|
||
| // Extract response data if available | ||
| val responseData = response?.let { | ||
| val responseHeaders = it.headers.toMap() | ||
| val (respBodySize, respBodyInfo) = extractBodyMetadata( | ||
| it.body?.contentLength(), | ||
| it.body?.contentType() | ||
| ) | ||
|
|
||
| scopes.options.logger.log( | ||
| io.sentry.SentryLevel.INFO, | ||
| "SentryNetwork: Response - Status: ${it.code}, Headers count: ${responseHeaders.size}, Body size: $respBodySize, Body info: $respBodyInfo" | ||
| ) | ||
|
|
||
| ReplayNetworkRequestOrResponse( | ||
| respBodySize, | ||
| respBodyInfo, | ||
| responseHeaders | ||
| ) | ||
| } | ||
|
|
||
| // Determine final body sizes (prefer the explicit sizes passed in) | ||
| val finalResponseBodySize = response?.let { | ||
| val (respBodySize, _) = extractBodyMetadata( | ||
| it.body?.contentLength(), | ||
| it.body?.contentType() | ||
| ) | ||
| responseBodySize ?: respBodySize | ||
| } | ||
|
|
||
| val networkData = NetworkRequestData( | ||
| request.method, | ||
| response?.code, | ||
| requestBodySize ?: reqBodySize, | ||
| finalResponseBodySize, | ||
| requestData, | ||
| responseData | ||
| ) | ||
|
|
||
| scopes.options.logger.log( | ||
| io.sentry.SentryLevel.INFO, | ||
| "SentryNetwork: Created NetworkRequestData: $networkData" | ||
| ) | ||
|
|
||
| return networkData | ||
| } | ||
|
|
||
| private fun finishSpan( | ||
| span: ISpan?, | ||
| request: Request, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.