From 1a52a5f690586129c98f874a539e39425510bdea Mon Sep 17 00:00:00 2001 From: Richard Harrah Date: Fri, 17 Nov 2023 14:36:35 -0500 Subject: [PATCH 1/7] add sentry-okhttp module partially resolves getsentry/sentry-java#1783 --- .craft.yml | 1 + .github/ISSUE_TEMPLATE/bug_report_java.yml | 1 + CHANGELOG.md | 6 + buildSrc/src/main/java/Config.kt | 1 + .../api/sentry-android-okhttp.api | 40 +- sentry-android-okhttp/build.gradle.kts | 5 +- .../okhttp/SentryOkHttpEventListener.kt | 368 +--------------- .../android/okhttp/SentryOkHttpInterceptor.kt | 204 ++------- .../android/okhttp/SentryOkHttpUtils.kt | 90 +--- sentry-okhttp/api/sentry-okhttp.api | 68 +++ sentry-okhttp/build.gradle.kts | 92 ++++ .../io/sentry}/okhttp/SentryOkHttpEvent.kt | 16 +- .../okhttp/SentryOkHttpEventListener.kt | 411 ++++++++++++++++++ .../sentry/okhttp/SentryOkHttpInterceptor.kt | 219 ++++++++++ .../io/sentry/okhttp/SentryOkHttpUtils.kt | 96 ++++ .../META-INF/proguard/sentry-okhttp.pro | 13 + .../okhttp/SentryOkHttpEventListenerTest.kt | 2 +- .../sentry}/okhttp/SentryOkHttpEventTest.kt | 16 +- .../okhttp/SentryOkHttpInterceptorTest.kt | 2 +- .../sentry}/okhttp/SentryOkHttpUtilsTest.kt | 2 +- .../org.mockito.plugin.MockMaker | 0 settings.gradle.kts | 1 + 22 files changed, 983 insertions(+), 671 deletions(-) create mode 100644 sentry-okhttp/api/sentry-okhttp.api create mode 100644 sentry-okhttp/build.gradle.kts rename {sentry-android-okhttp/src/main/java/io/sentry/android => sentry-okhttp/src/main/java/io/sentry}/okhttp/SentryOkHttpEvent.kt (92%) create mode 100644 sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEventListener.kt create mode 100644 sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt create mode 100644 sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt create mode 100644 sentry-okhttp/src/main/resources/META-INF/proguard/sentry-okhttp.pro rename {sentry-android-okhttp/src/test/java/io/sentry/android => sentry-okhttp/src/test/java/io/sentry}/okhttp/SentryOkHttpEventListenerTest.kt (99%) rename {sentry-android-okhttp/src/test/java/io/sentry/android => sentry-okhttp/src/test/java/io/sentry}/okhttp/SentryOkHttpEventTest.kt (97%) rename {sentry-android-okhttp/src/test/java/io/sentry/android => sentry-okhttp/src/test/java/io/sentry}/okhttp/SentryOkHttpInterceptorTest.kt (99%) rename {sentry-android-okhttp/src/test/java/io/sentry/android => sentry-okhttp/src/test/java/io/sentry}/okhttp/SentryOkHttpUtilsTest.kt (99%) rename sentry-android-okhttp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker => sentry-okhttp/src/test/resources/mockito-extensions/org.mockito.plugin.MockMaker (100%) diff --git a/.craft.yml b/.craft.yml index f3f0f52c9a4..28c7191380e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -49,6 +49,7 @@ targets: maven:io.sentry:sentry-jdbc: maven:io.sentry:sentry-graphql: maven:io.sentry:sentry-quartz: + # maven:io.sentry:sentry-okhttp: maven:io.sentry:sentry-android-navigation: maven:io.sentry:sentry-compose: maven:io.sentry:sentry-compose-android: diff --git a/.github/ISSUE_TEMPLATE/bug_report_java.yml b/.github/ISSUE_TEMPLATE/bug_report_java.yml index 429fbdee73a..f802c3a0cc1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_java.yml +++ b/.github/ISSUE_TEMPLATE/bug_report_java.yml @@ -30,6 +30,7 @@ body: - sentry-quartz - sentry-openfeign - sentry-apache-http-client-5 + - sentry-okhttp - other validations: required: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c570959a5a..0463aba4dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unrelease + +### Features + +- Add `sentry-okhttp` module to support instrumenting OkHttp in non-Android projects + ## 7.0.0-rc.1 ### Features diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index e2cbf460af5..6c81e13a20a 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -236,6 +236,7 @@ object Config { val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet" val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta" val SENTRY_COMPOSE_HELPER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.compose.helper" + val SENTRY_OKHTTP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.okhttp" val group = "io.sentry" val description = "SDK for sentry.io" val versionNameProp = "versionName" diff --git a/sentry-android-okhttp/api/sentry-android-okhttp.api b/sentry-android-okhttp/api/sentry-android-okhttp.api index 099534ea61e..052ccc476f5 100644 --- a/sentry-android-okhttp/api/sentry-android-okhttp.api +++ b/sentry-android-okhttp/api/sentry-android-okhttp.api @@ -6,8 +6,7 @@ public final class io/sentry/android/okhttp/BuildConfig { public fun ()V } -public final class io/sentry/android/okhttp/SentryOkHttpEventListener : okhttp3/EventListener { - public static final field Companion Lio/sentry/android/okhttp/SentryOkHttpEventListener$Companion; +public final class io/sentry/android/okhttp/SentryOkHttpEventListener : io/sentry/okhttp/SentryOkHttpEventListener { public fun ()V public fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;)V public synthetic fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -17,47 +16,16 @@ public final class io/sentry/android/okhttp/SentryOkHttpEventListener : okhttp3/ public synthetic fun (Lio/sentry/IHub;Lokhttp3/EventListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lokhttp3/EventListener$Factory;)V public fun (Lokhttp3/EventListener;)V - public fun cacheConditionalHit (Lokhttp3/Call;Lokhttp3/Response;)V - public fun cacheHit (Lokhttp3/Call;Lokhttp3/Response;)V - public fun cacheMiss (Lokhttp3/Call;)V - public fun callEnd (Lokhttp3/Call;)V - public fun callFailed (Lokhttp3/Call;Ljava/io/IOException;)V - public fun callStart (Lokhttp3/Call;)V - public fun canceled (Lokhttp3/Call;)V - public fun connectEnd (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;)V - public fun connectFailed (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;Ljava/io/IOException;)V - public fun connectStart (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;)V - public fun connectionAcquired (Lokhttp3/Call;Lokhttp3/Connection;)V - public fun connectionReleased (Lokhttp3/Call;Lokhttp3/Connection;)V - public fun dnsEnd (Lokhttp3/Call;Ljava/lang/String;Ljava/util/List;)V - public fun dnsStart (Lokhttp3/Call;Ljava/lang/String;)V - public fun proxySelectEnd (Lokhttp3/Call;Lokhttp3/HttpUrl;Ljava/util/List;)V - public fun proxySelectStart (Lokhttp3/Call;Lokhttp3/HttpUrl;)V - public fun requestBodyEnd (Lokhttp3/Call;J)V - public fun requestBodyStart (Lokhttp3/Call;)V - public fun requestFailed (Lokhttp3/Call;Ljava/io/IOException;)V - public fun requestHeadersEnd (Lokhttp3/Call;Lokhttp3/Request;)V - public fun requestHeadersStart (Lokhttp3/Call;)V - public fun responseBodyEnd (Lokhttp3/Call;J)V - public fun responseBodyStart (Lokhttp3/Call;)V - public fun responseFailed (Lokhttp3/Call;Ljava/io/IOException;)V - public fun responseHeadersEnd (Lokhttp3/Call;Lokhttp3/Response;)V - public fun responseHeadersStart (Lokhttp3/Call;)V - public fun satisfactionFailure (Lokhttp3/Call;Lokhttp3/Response;)V - public fun secureConnectEnd (Lokhttp3/Call;Lokhttp3/Handshake;)V - public fun secureConnectStart (Lokhttp3/Call;)V } -public final class io/sentry/android/okhttp/SentryOkHttpEventListener$Companion { -} - -public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor { +public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : io/sentry/okhttp/SentryOkHttpInterceptor, okhttp3/Interceptor { public fun ()V public fun (Lio/sentry/IHub;)V public fun (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V public synthetic fun (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V + public synthetic fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V - public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; } public abstract interface class io/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback { diff --git a/sentry-android-okhttp/build.gradle.kts b/sentry-android-okhttp/build.gradle.kts index 0f98011fac9..f3eaa593036 100644 --- a/sentry-android-okhttp/build.gradle.kts +++ b/sentry-android-okhttp/build.gradle.kts @@ -24,9 +24,7 @@ android { buildTypes { getByName("debug") - getByName("release") { - consumerProguardFiles("proguard-rules.pro") - } + getByName("release") } kotlinOptions { @@ -63,6 +61,7 @@ kotlin { dependencies { api(projects.sentry) + api(projects.sentryOkhttp) compileOnly(Config.Libs.okhttp) diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt index da13ee110a1..dfcaa4819d5 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt @@ -2,26 +2,13 @@ package io.sentry.android.okhttp import io.sentry.HubAdapter import io.sentry.IHub -import io.sentry.SpanDataConvention -import io.sentry.SpanStatus import okhttp3.Call -import okhttp3.Connection import okhttp3.EventListener -import okhttp3.Handshake -import okhttp3.HttpUrl -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import java.io.IOException -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.Proxy -import java.util.concurrent.ConcurrentHashMap /** * Logs network performance event metrics to Sentry * - * Usage - add instance of [SentryOkHttpEventListener] in [OkHttpClient.eventListener] + * Usage - add instance of [SentryOkHttpEventListener] in [okhttp3.OkHttpClient.Builder.eventListener] * * ``` * val client = OkHttpClient.Builder() @@ -30,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap * .build() * ``` * - * If you already use a [OkHttpClient.eventListener], you can pass it in the constructor. + * If you already use a [okhttp3.EventListener], you can pass it in the constructor. * * ``` * val client = OkHttpClient.Builder() @@ -39,28 +26,14 @@ import java.util.concurrent.ConcurrentHashMap * .build() * ``` */ -@Suppress("TooManyFunctions") +@Deprecated( + "Use SentryOkHttpEventListener from sentry-okhttp instead", + ReplaceWith("SentryOkHttpEventListener", "io.sentry.okhttp.SentryOkHttpEventListener") +) class SentryOkHttpEventListener( - private val hub: IHub = HubAdapter.getInstance(), - private val originalEventListenerCreator: ((call: Call) -> EventListener)? = null -) : EventListener() { - - private var originalEventListener: EventListener? = null - - companion object { - internal const val PROXY_SELECT_EVENT = "proxy_select" - internal const val DNS_EVENT = "dns" - internal const val SECURE_CONNECT_EVENT = "secure_connect" - internal const val CONNECT_EVENT = "connect" - internal const val CONNECTION_EVENT = "connection" - internal const val REQUEST_HEADERS_EVENT = "request_headers" - internal const val REQUEST_BODY_EVENT = "request_body" - internal const val RESPONSE_HEADERS_EVENT = "response_headers" - internal const val RESPONSE_BODY_EVENT = "response_body" - - internal val eventMap: MutableMap = ConcurrentHashMap() - } - + hub: IHub = HubAdapter.getInstance(), + originalEventListenerCreator: ((call: Call) -> EventListener)? = null +) : io.sentry.okhttp.SentryOkHttpEventListener(hub, originalEventListenerCreator) { constructor() : this( HubAdapter.getInstance(), originalEventListenerCreator = null @@ -85,327 +58,4 @@ class SentryOkHttpEventListener( hub, originalEventListenerCreator = { originalEventListenerFactory.create(it) } ) - - override fun callStart(call: Call) { - originalEventListener = originalEventListenerCreator?.invoke(call) - originalEventListener?.callStart(call) - // If the wrapped EventListener is ours, we can just delegate the calls, - // without creating other events that would create duplicates - if (canCreateEventSpan()) { - eventMap[call] = SentryOkHttpEvent(hub, call.request()) - } - } - - override fun proxySelectStart(call: Call, url: HttpUrl) { - originalEventListener?.proxySelectStart(call, url) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(PROXY_SELECT_EVENT) - } - - override fun proxySelectEnd( - call: Call, - url: HttpUrl, - proxies: List - ) { - originalEventListener?.proxySelectEnd(call, url, proxies) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(PROXY_SELECT_EVENT) { - if (proxies.isNotEmpty()) { - it.setData("proxies", proxies.joinToString { proxy -> proxy.toString() }) - } - } - } - - override fun dnsStart(call: Call, domainName: String) { - originalEventListener?.dnsStart(call, domainName) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(DNS_EVENT) - } - - override fun dnsEnd( - call: Call, - domainName: String, - inetAddressList: List - ) { - originalEventListener?.dnsEnd(call, domainName, inetAddressList) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(DNS_EVENT) { - it.setData("domain_name", domainName) - if (inetAddressList.isNotEmpty()) { - it.setData("dns_addresses", inetAddressList.joinToString { address -> address.toString() }) - } - } - } - - override fun connectStart( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy - ) { - originalEventListener?.connectStart(call, inetSocketAddress, proxy) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(CONNECT_EVENT) - } - - override fun secureConnectStart(call: Call) { - originalEventListener?.secureConnectStart(call) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(SECURE_CONNECT_EVENT) - } - - override fun secureConnectEnd(call: Call, handshake: Handshake?) { - originalEventListener?.secureConnectEnd(call, handshake) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(SECURE_CONNECT_EVENT) - } - - override fun connectEnd( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy, - protocol: Protocol? - ) { - originalEventListener?.connectEnd(call, inetSocketAddress, proxy, protocol) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setProtocol(protocol?.name) - okHttpEvent.finishSpan(CONNECT_EVENT) - } - - override fun connectFailed( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy, - protocol: Protocol?, - ioe: IOException - ) { - originalEventListener?.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setProtocol(protocol?.name) - okHttpEvent.setError(ioe.message) - okHttpEvent.finishSpan(CONNECT_EVENT) { - it.throwable = ioe - it.status = SpanStatus.INTERNAL_ERROR - } - } - - override fun connectionAcquired(call: Call, connection: Connection) { - originalEventListener?.connectionAcquired(call, connection) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(CONNECTION_EVENT) - } - - override fun connectionReleased(call: Call, connection: Connection) { - originalEventListener?.connectionReleased(call, connection) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(CONNECTION_EVENT) - } - - override fun requestHeadersStart(call: Call) { - originalEventListener?.requestHeadersStart(call) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(REQUEST_HEADERS_EVENT) - } - - override fun requestHeadersEnd(call: Call, request: Request) { - originalEventListener?.requestHeadersEnd(call, request) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(REQUEST_HEADERS_EVENT) - } - - override fun requestBodyStart(call: Call) { - originalEventListener?.requestBodyStart(call) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(REQUEST_BODY_EVENT) - } - - override fun requestBodyEnd(call: Call, byteCount: Long) { - originalEventListener?.requestBodyEnd(call, byteCount) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.finishSpan(REQUEST_BODY_EVENT) { - if (byteCount > 0) { - it.setData("http.request_content_length", byteCount) - } - } - okHttpEvent.setRequestBodySize(byteCount) - } - - override fun requestFailed(call: Call, ioe: IOException) { - originalEventListener?.requestFailed(call, ioe) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setError(ioe.message) - // requestFailed can happen after requestHeaders or requestBody. - // If requestHeaders already finished, we don't change its status. - okHttpEvent.finishSpan(REQUEST_HEADERS_EVENT) { - if (!it.isFinished) { - it.status = SpanStatus.INTERNAL_ERROR - it.throwable = ioe - } - } - okHttpEvent.finishSpan(REQUEST_BODY_EVENT) { - it.status = SpanStatus.INTERNAL_ERROR - it.throwable = ioe - } - } - - override fun responseHeadersStart(call: Call) { - originalEventListener?.responseHeadersStart(call) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(RESPONSE_HEADERS_EVENT) - } - - override fun responseHeadersEnd(call: Call, response: Response) { - originalEventListener?.responseHeadersEnd(call, response) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setResponse(response) - val responseHeadersSpan = okHttpEvent.finishSpan(RESPONSE_HEADERS_EVENT) { - it.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, response.code) - // Let's not override the status of a span that was set - if (it.status == null) { - it.status = SpanStatus.fromHttpStatusCode(response.code) - } - } - okHttpEvent.scheduleFinish(responseHeadersSpan?.finishDate ?: hub.options.dateProvider.now()) - } - - override fun responseBodyStart(call: Call) { - originalEventListener?.responseBodyStart(call) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.startSpan(RESPONSE_BODY_EVENT) - } - - override fun responseBodyEnd(call: Call, byteCount: Long) { - originalEventListener?.responseBodyEnd(call, byteCount) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setResponseBodySize(byteCount) - okHttpEvent.finishSpan(RESPONSE_BODY_EVENT) { - if (byteCount > 0) { - it.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, byteCount) - } - } - } - - override fun responseFailed(call: Call, ioe: IOException) { - originalEventListener?.responseFailed(call, ioe) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return - okHttpEvent.setError(ioe.message) - // responseFailed can happen after responseHeaders or responseBody. - // If responseHeaders already finished, we don't change its status. - okHttpEvent.finishSpan(RESPONSE_HEADERS_EVENT) { - if (!it.isFinished) { - it.status = SpanStatus.INTERNAL_ERROR - it.throwable = ioe - } - } - okHttpEvent.finishSpan(RESPONSE_BODY_EVENT) { - it.status = SpanStatus.INTERNAL_ERROR - it.throwable = ioe - } - } - - override fun callEnd(call: Call) { - originalEventListener?.callEnd(call) - val okHttpEvent: SentryOkHttpEvent = eventMap.remove(call) ?: return - okHttpEvent.finishEvent() - } - - override fun callFailed(call: Call, ioe: IOException) { - originalEventListener?.callFailed(call, ioe) - if (!canCreateEventSpan()) { - return - } - val okHttpEvent: SentryOkHttpEvent = eventMap.remove(call) ?: return - okHttpEvent.setError(ioe.message) - okHttpEvent.finishEvent { - it.status = SpanStatus.INTERNAL_ERROR - it.throwable = ioe - } - } - - override fun canceled(call: Call) { - originalEventListener?.canceled(call) - } - - override fun satisfactionFailure(call: Call, response: Response) { - originalEventListener?.satisfactionFailure(call, response) - } - - override fun cacheHit(call: Call, response: Response) { - originalEventListener?.cacheHit(call, response) - } - - override fun cacheMiss(call: Call) { - originalEventListener?.cacheMiss(call) - } - - override fun cacheConditionalHit(call: Call, cachedResponse: Response) { - originalEventListener?.cacheConditionalHit(call, cachedResponse) - } - - private fun canCreateEventSpan(): Boolean { - // If the wrapped EventListener is ours, we shouldn't create spans, as the originalEventListener already did it - return originalEventListener !is SentryOkHttpEventListener - } } diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt index ddefaabbde3..e4d6ff1e7b2 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt @@ -1,27 +1,16 @@ package io.sentry.android.okhttp -import io.sentry.BaggageHeader -import io.sentry.Breadcrumb -import io.sentry.Hint import io.sentry.HttpStatusCodeRange import io.sentry.HubAdapter import io.sentry.IHub import io.sentry.ISpan import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS -import io.sentry.SpanDataConvention -import io.sentry.SpanStatus -import io.sentry.TypeCheckHint.OKHTTP_REQUEST -import io.sentry.TypeCheckHint.OKHTTP_RESPONSE +import io.sentry.android.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion -import io.sentry.util.Platform -import io.sentry.util.PropagationTargetsUtils -import io.sentry.util.TracingUtils -import io.sentry.util.UrlUtils import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import java.io.IOException /** * The Sentry's [SentryOkHttpInterceptor], it will automatically add a breadcrumb and start a span @@ -37,19 +26,46 @@ import java.io.IOException * @param failedRequestTargets The SDK will only capture HTTP Client errors if the HTTP Request URL * is a match for any of the defined targets. */ +@Deprecated( + "Use SentryOkHttpInterceptor from sentry-okhttp instead", + ReplaceWith("SentryOkHttpInterceptor", "io.sentry.okhttp.SentryOkHttpInterceptor") +) class SentryOkHttpInterceptor( private val hub: IHub = HubAdapter.getInstance(), - private val beforeSpan: BeforeSpanCallback? = null, + private val beforeSpan: io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback? = null, private val captureFailedRequests: Boolean = true, private val failedRequestStatusCodes: List = listOf( HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) ), private val failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) -) : Interceptor { +) : io.sentry.okhttp.SentryOkHttpInterceptor( + hub, + beforeSpan, + captureFailedRequests, + failedRequestStatusCodes, + failedRequestTargets +), Interceptor { constructor() : this(HubAdapter.getInstance()) - constructor(hub: IHub) : this(hub, null) + constructor(hub: IHub) : this(hub, null as io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback?) constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) + constructor( + hub: IHub, + beforeSpan: BeforeSpanCallback? = null, + captureFailedRequests: Boolean = false, + failedRequestStatusCodes: List = listOf( + HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) + ), + failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) + ) : this( + hub, + io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback { span, request, response -> + beforeSpan?.execute(span, request, response) + }, + captureFailedRequests, + failedRequestStatusCodes, + failedRequestTargets + ) init { addIntegrationToSdkVersion(javaClass) @@ -57,163 +73,13 @@ class SentryOkHttpInterceptor( .addPackage("maven:io.sentry:sentry-android-okhttp", BuildConfig.VERSION_NAME) } - @Suppress("LongMethod") - override fun intercept(chain: Interceptor.Chain): Response { - var request = chain.request() - - val urlDetails = UrlUtils.parse(request.url.toString()) - val url = urlDetails.urlOrFallback - val method = request.method - - val span: ISpan? - val okHttpEvent: SentryOkHttpEvent? - - if (SentryOkHttpEventListener.eventMap.containsKey(chain.call())) { - // read the span from the event listener - okHttpEvent = SentryOkHttpEventListener.eventMap[chain.call()] - span = okHttpEvent?.callRootSpan - } else { - // read the span from the bound scope - okHttpEvent = null - val parentSpan = if (Platform.isAndroid()) hub.transaction else hub.span - span = parentSpan?.startChild("http.client", "$method $url") - } - - span?.spanContext?.origin = TRACE_ORIGIN - - urlDetails.applyToSpan(span) - - val isFromEventListener = okHttpEvent != null - var response: Response? = null - var code: Int? = null - - try { - val requestBuilder = request.newBuilder() - - TracingUtils.traceIfAllowed( - hub, - request.url.toString(), - request.headers(BaggageHeader.BAGGAGE_HEADER), - span - )?.let { tracingHeaders -> - requestBuilder.addHeader(tracingHeaders.sentryTraceHeader.name, tracingHeaders.sentryTraceHeader.value) - tracingHeaders.baggageHeader?.let { - requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER) - requestBuilder.addHeader(it.name, it.value) - } - } - - request = requestBuilder.build() - response = chain.proceed(request) - code = response.code - span?.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, code) - span?.status = SpanStatus.fromHttpStatusCode(code) - - // OkHttp errors (4xx, 5xx) don't throw, so it's safe to call within this block. - // breadcrumbs are added on the finally block because we'd like to know if the device - // had an unstable connection or something similar - if (shouldCaptureClientError(request, response)) { - // If we capture the client error directly, it could be associated with the - // currently running span by the backend. In case the listener is in use, that is - // an inner span. So, if the listener is in use, we let it capture the client - // error, to shown it in the http root call span in the dashboard. - if (isFromEventListener && okHttpEvent != null) { - okHttpEvent.setClientErrorResponse(response) - } else { - SentryOkHttpUtils.captureClientError(hub, request, response) - } - } - - return response - } catch (e: IOException) { - span?.apply { - this.throwable = e - this.status = SpanStatus.INTERNAL_ERROR - } - throw e - } finally { - finishSpan(span, request, response, isFromEventListener) - - // The SentryOkHttpEventListener will send the breadcrumb itself if used for this call - if (!isFromEventListener) { - sendBreadcrumb(request, code, response) - } - } - } - - private fun sendBreadcrumb(request: Request, code: Int?, response: Response?) { - val breadcrumb = Breadcrumb.http(request.url.toString(), request.method, code) - request.body?.contentLength().ifHasValidLength { - breadcrumb.setData("http.request_content_length", 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) - } - - hint[OKHTTP_RESPONSE] = it - } - - hub.addBreadcrumb(breadcrumb, hint) - } - - private fun finishSpan(span: ISpan?, request: Request, response: Response?, isFromEventListener: Boolean) { - if (span == null) { - return - } - if (beforeSpan != null) { - val result = beforeSpan.execute(span, request, response) - if (result == null) { - // span is dropped - span.spanContext.sampled = false - } else { - // The SentryOkHttpEventListener will finish the span itself if used for this call - if (!isFromEventListener) { - span.finish() - } - } - } else { - // The SentryOkHttpEventListener will finish the span itself if used for this call - if (!isFromEventListener) { - span.finish() - } - } - } - - private fun Long?.ifHasValidLength(fn: (Long) -> Unit) { - if (this != null && this != -1L) { - fn.invoke(this) - } - } - - private fun shouldCaptureClientError(request: Request, response: Response): Boolean { - // return if the feature is disabled or its not within the range - if (!captureFailedRequests || !containsStatusCode(response.code)) { - return false - } - - // return if its not a target match - if (!PropagationTargetsUtils.contain(failedRequestTargets, request.url.toString())) { - return false - } - - return true - } - - private fun containsStatusCode(statusCode: Int): Boolean { - for (item in failedRequestStatusCodes) { - if (item.isInRange(statusCode)) { - return true - } - } - return false - } - /** * The BeforeSpan callback */ + @Deprecated( + "Use BeforeSpanCallback from sentry-okhttp instead", + ReplaceWith("BeforeSpanCallback", "io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback") + ) fun interface BeforeSpanCallback { /** * Mutates or drops span before being added diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt index 8d9a24edbc8..390e70aaf8e 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt @@ -1,96 +1,16 @@ package io.sentry.android.okhttp -import io.sentry.Hint import io.sentry.IHub -import io.sentry.SentryEvent -import io.sentry.TypeCheckHint -import io.sentry.exception.ExceptionMechanismException -import io.sentry.exception.SentryHttpClientException -import io.sentry.protocol.Mechanism -import io.sentry.util.HttpUtils -import io.sentry.util.UrlUtils -import okhttp3.Headers import okhttp3.Request import okhttp3.Response +@Deprecated( + "Use SentryOkHttpUtils from sentry-okhttp instead", + ReplaceWith("SentryOkHttpUtils", "io.sentry.okhttp.SentryOkHttpUtils") +) object SentryOkHttpUtils { fun captureClientError(hub: IHub, request: Request, response: Response) { - // not possible to get a parameterized url, but we remove at least the - // query string and the fragment. - // url example: https://api.github.com/users/getsentry/repos/#fragment?query=query - // url will be: https://api.github.com/users/getsentry/repos/ - // ideally we'd like a parameterized url: https://api.github.com/users/{user}/repos/ - // but that's not possible - val urlDetails = UrlUtils.parse(request.url.toString()) - - val mechanism = Mechanism().apply { - type = "SentryOkHttpInterceptor" - } - val exception = SentryHttpClientException( - "HTTP Client Error with status code: ${response.code}" - ) - val mechanismException = ExceptionMechanismException(mechanism, exception, Thread.currentThread(), true) - val event = SentryEvent(mechanismException) - - val hint = Hint() - hint.set(TypeCheckHint.OKHTTP_REQUEST, request) - hint.set(TypeCheckHint.OKHTTP_RESPONSE, response) - - val sentryRequest = io.sentry.protocol.Request().apply { - urlDetails.applyToRequest(this) - // Cookie is only sent if isSendDefaultPii is enabled - cookies = if (hub.options.isSendDefaultPii) request.headers["Cookie"] else null - method = request.method - headers = getHeaders(hub, request.headers) - - request.body?.contentLength().ifHasValidLength { - bodySize = it - } - } - - val sentryResponse = io.sentry.protocol.Response().apply { - // Set-Cookie is only sent if isSendDefaultPii is enabled due to PII - cookies = if (hub.options.isSendDefaultPii) response.headers["Set-Cookie"] else null - headers = getHeaders(hub, response.headers) - statusCode = response.code - - response.body?.contentLength().ifHasValidLength { - bodySize = it - } - } - - event.request = sentryRequest - event.contexts.setResponse(sentryResponse) - - hub.captureEvent(event, hint) - } - - private fun Long?.ifHasValidLength(fn: (Long) -> Unit) { - if (this != null && this != -1L) { - fn.invoke(this) - } - } - - private fun getHeaders(hub: IHub, requestHeaders: Headers): MutableMap? { - // Headers are only sent if isSendDefaultPii is enabled due to PII - if (!hub.options.isSendDefaultPii) { - return null - } - - val headers = mutableMapOf() - - for (i in 0 until requestHeaders.size) { - val name = requestHeaders.name(i) - - // header is only sent if isn't sensitive - if (HttpUtils.containsSensitiveHeader(name)) { - continue - } - - val value = requestHeaders.value(i) - headers[name] = value - } - return headers + io.sentry.okhttp.SentryOkHttpUtils.captureClientError(hub, request, response) } } diff --git a/sentry-okhttp/api/sentry-okhttp.api b/sentry-okhttp/api/sentry-okhttp.api new file mode 100644 index 00000000000..02cf0581151 --- /dev/null +++ b/sentry-okhttp/api/sentry-okhttp.api @@ -0,0 +1,68 @@ +public final class io/sentry/okhttp/BuildConfig { + public static final field SENTRY_OKHTTP_SDK_NAME Ljava/lang/String; + public static final field VERSION_NAME Ljava/lang/String; +} + +public final class io/sentry/okhttp/SentryOkHttpEventListener : okhttp3/EventListener { + public static final field Companion Lio/sentry/okhttp/SentryOkHttpEventListener$Companion; + public fun ()V + public fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/IHub;Lokhttp3/EventListener$Factory;)V + public synthetic fun (Lio/sentry/IHub;Lokhttp3/EventListener$Factory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/IHub;Lokhttp3/EventListener;)V + public synthetic fun (Lio/sentry/IHub;Lokhttp3/EventListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lokhttp3/EventListener$Factory;)V + public fun (Lokhttp3/EventListener;)V + public fun cacheConditionalHit (Lokhttp3/Call;Lokhttp3/Response;)V + public fun cacheHit (Lokhttp3/Call;Lokhttp3/Response;)V + public fun cacheMiss (Lokhttp3/Call;)V + public fun callEnd (Lokhttp3/Call;)V + public fun callFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun callStart (Lokhttp3/Call;)V + public fun canceled (Lokhttp3/Call;)V + public fun connectEnd (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;)V + public fun connectFailed (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;Ljava/io/IOException;)V + public fun connectStart (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;)V + public fun connectionAcquired (Lokhttp3/Call;Lokhttp3/Connection;)V + public fun connectionReleased (Lokhttp3/Call;Lokhttp3/Connection;)V + public fun dnsEnd (Lokhttp3/Call;Ljava/lang/String;Ljava/util/List;)V + public fun dnsStart (Lokhttp3/Call;Ljava/lang/String;)V + public fun proxySelectEnd (Lokhttp3/Call;Lokhttp3/HttpUrl;Ljava/util/List;)V + public fun proxySelectStart (Lokhttp3/Call;Lokhttp3/HttpUrl;)V + public fun requestBodyEnd (Lokhttp3/Call;J)V + public fun requestBodyStart (Lokhttp3/Call;)V + public fun requestFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun requestHeadersEnd (Lokhttp3/Call;Lokhttp3/Request;)V + public fun requestHeadersStart (Lokhttp3/Call;)V + public fun responseBodyEnd (Lokhttp3/Call;J)V + public fun responseBodyStart (Lokhttp3/Call;)V + public fun responseFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun responseHeadersEnd (Lokhttp3/Call;Lokhttp3/Response;)V + public fun responseHeadersStart (Lokhttp3/Call;)V + public fun satisfactionFailure (Lokhttp3/Call;Lokhttp3/Response;)V + public fun secureConnectEnd (Lokhttp3/Call;Lokhttp3/Handshake;)V + public fun secureConnectStart (Lokhttp3/Call;)V +} + +public final class io/sentry/okhttp/SentryOkHttpEventListener$Companion { +} + +public final class io/sentry/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor { + public fun ()V + public fun (Lio/sentry/IHub;)V + public fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V + public synthetic fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V + public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; +} + +public abstract interface class io/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback { + public abstract fun execute (Lio/sentry/ISpan;Lokhttp3/Request;Lokhttp3/Response;)Lio/sentry/ISpan; +} + +public final class io/sentry/okhttp/SentryOkHttpUtils { + public static final field INSTANCE Lio/sentry/okhttp/SentryOkHttpUtils; + public final fun captureClientError (Lio/sentry/IHub;Lokhttp3/Request;Lokhttp3/Response;)V +} + diff --git a/sentry-okhttp/build.gradle.kts b/sentry-okhttp/build.gradle.kts new file mode 100644 index 00000000000..a30e2d0594b --- /dev/null +++ b/sentry-okhttp/build.gradle.kts @@ -0,0 +1,92 @@ +import net.ltgt.gradle.errorprone.errorprone +import org.jetbrains.kotlin.config.KotlinCompilerVersion +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `java-library` + kotlin("jvm") + jacoco + id(Config.QualityPlugins.errorProne) + id(Config.QualityPlugins.gradleVersions) + id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +} + +kotlin { + explicitApi() +} + +dependencies { + api(projects.sentry) + + compileOnly(Config.Libs.okhttp) + + implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) + + compileOnly(Config.CompileOnly.nopen) + errorprone(Config.CompileOnly.nopenChecker) + errorprone(Config.CompileOnly.errorprone) + errorprone(Config.CompileOnly.errorProneNullAway) + compileOnly(Config.CompileOnly.jetbrainsAnnotations) + + // tests + testImplementation(projects.sentryTestSupport) + testImplementation(Config.Libs.okhttp) + testImplementation(Config.TestLibs.kotlinTestJunit) + testImplementation(Config.TestLibs.mockitoKotlin) + testImplementation(Config.TestLibs.mockitoInline) + testImplementation(Config.TestLibs.mockWebserver) +} + +configure { + test { + java.srcDir("src/test/java") + } +} + +jacoco { + toolVersion = Config.QualityPlugins.Jacoco.version +} + +tasks.jacocoTestReport { + reports { + xml.required.set(true) + html.required.set(false) + } +} + +tasks { + jacocoTestCoverageVerification { + violationRules { + rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } + } + } + check { + dependsOn(jacocoTestCoverageVerification) + dependsOn(jacocoTestReport) + } +} + +buildConfig { + useJavaOutput() + packageName("io.sentry.okhttp") + buildConfigField("String", "SENTRY_OKHTTP_SDK_NAME", "\"${Config.Sentry.SENTRY_OKHTTP_SDK_NAME}\"") + buildConfigField("String", "VERSION_NAME", "\"${project.version}\"") +} + +val generateBuildConfig by tasks +tasks.withType().configureEach { + dependsOn(generateBuildConfig) + options.errorprone { + check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "io.sentry") + } +} diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEvent.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEvent.kt similarity index 92% rename from sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEvent.kt rename to sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEvent.kt index e38445afaca..4870c8bb64b 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEvent.kt +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEvent.kt @@ -1,4 +1,4 @@ -package io.sentry.android.okhttp +package io.sentry.okhttp import io.sentry.Breadcrumb import io.sentry.Hint @@ -9,13 +9,13 @@ import io.sentry.SentryLevel import io.sentry.SpanDataConvention import io.sentry.SpanStatus import io.sentry.TypeCheckHint -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT import io.sentry.util.Platform import io.sentry.util.UrlUtils import okhttp3.Request diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEventListener.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEventListener.kt new file mode 100644 index 00000000000..67a8cd8b563 --- /dev/null +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEventListener.kt @@ -0,0 +1,411 @@ +package io.sentry.okhttp + +import io.sentry.HubAdapter +import io.sentry.IHub +import io.sentry.SpanDataConvention +import io.sentry.SpanStatus +import okhttp3.Call +import okhttp3.Connection +import okhttp3.EventListener +import okhttp3.Handshake +import okhttp3.HttpUrl +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Proxy +import java.util.concurrent.ConcurrentHashMap + +/** + * Logs network performance event metrics to Sentry + * + * Usage - add instance of [SentryOkHttpEventListener] in [okhttp3.OkHttpClient.Builder.eventListener] + * + * ``` + * val client = OkHttpClient.Builder() + * .eventListener(SentryOkHttpEventListener()) + * .addInterceptor(SentryOkHttpInterceptor()) + * .build() + * ``` + * + * If you already use a [okhttp3.EventListener], you can pass it in the constructor. + * + * ``` + * val client = OkHttpClient.Builder() + * .eventListener(SentryOkHttpEventListener(myEventListener)) + * .addInterceptor(SentryOkHttpInterceptor()) + * .build() + * ``` + */ +@Suppress("TooManyFunctions") +public open class SentryOkHttpEventListener( + private val hub: IHub = HubAdapter.getInstance(), + private val originalEventListenerCreator: ((call: Call) -> EventListener)? = null +) : EventListener() { + + private var originalEventListener: EventListener? = null + + public companion object { + internal const val PROXY_SELECT_EVENT = "proxy_select" + internal const val DNS_EVENT = "dns" + internal const val SECURE_CONNECT_EVENT = "secure_connect" + internal const val CONNECT_EVENT = "connect" + internal const val CONNECTION_EVENT = "connection" + internal const val REQUEST_HEADERS_EVENT = "request_headers" + internal const val REQUEST_BODY_EVENT = "request_body" + internal const val RESPONSE_HEADERS_EVENT = "response_headers" + internal const val RESPONSE_BODY_EVENT = "response_body" + + internal val eventMap: MutableMap = ConcurrentHashMap() + } + + public constructor() : this( + HubAdapter.getInstance(), + originalEventListenerCreator = null + ) + + public constructor(originalEventListener: EventListener) : this( + HubAdapter.getInstance(), + originalEventListenerCreator = { originalEventListener } + ) + + public constructor(originalEventListenerFactory: Factory) : this( + HubAdapter.getInstance(), + originalEventListenerCreator = { originalEventListenerFactory.create(it) } + ) + + public constructor(hub: IHub = HubAdapter.getInstance(), originalEventListener: EventListener) : this( + hub, + originalEventListenerCreator = { originalEventListener } + ) + + public constructor(hub: IHub = HubAdapter.getInstance(), originalEventListenerFactory: Factory) : this( + hub, + originalEventListenerCreator = { originalEventListenerFactory.create(it) } + ) + + override fun callStart(call: Call) { + originalEventListener = originalEventListenerCreator?.invoke(call) + originalEventListener?.callStart(call) + // If the wrapped EventListener is ours, we can just delegate the calls, + // without creating other events that would create duplicates + if (canCreateEventSpan()) { + eventMap[call] = SentryOkHttpEvent(hub, call.request()) + } + } + + override fun proxySelectStart(call: Call, url: HttpUrl) { + originalEventListener?.proxySelectStart(call, url) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(PROXY_SELECT_EVENT) + } + + override fun proxySelectEnd( + call: Call, + url: HttpUrl, + proxies: List + ) { + originalEventListener?.proxySelectEnd(call, url, proxies) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(PROXY_SELECT_EVENT) { + if (proxies.isNotEmpty()) { + it.setData("proxies", proxies.joinToString { proxy -> proxy.toString() }) + } + } + } + + override fun dnsStart(call: Call, domainName: String) { + originalEventListener?.dnsStart(call, domainName) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(DNS_EVENT) + } + + override fun dnsEnd( + call: Call, + domainName: String, + inetAddressList: List + ) { + originalEventListener?.dnsEnd(call, domainName, inetAddressList) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(DNS_EVENT) { + it.setData("domain_name", domainName) + if (inetAddressList.isNotEmpty()) { + it.setData("dns_addresses", inetAddressList.joinToString { address -> address.toString() }) + } + } + } + + override fun connectStart( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy + ) { + originalEventListener?.connectStart(call, inetSocketAddress, proxy) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(CONNECT_EVENT) + } + + override fun secureConnectStart(call: Call) { + originalEventListener?.secureConnectStart(call) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(SECURE_CONNECT_EVENT) + } + + override fun secureConnectEnd(call: Call, handshake: Handshake?) { + originalEventListener?.secureConnectEnd(call, handshake) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(SECURE_CONNECT_EVENT) + } + + override fun connectEnd( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol? + ) { + originalEventListener?.connectEnd(call, inetSocketAddress, proxy, protocol) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setProtocol(protocol?.name) + okHttpEvent.finishSpan(CONNECT_EVENT) + } + + override fun connectFailed( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol?, + ioe: IOException + ) { + originalEventListener?.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setProtocol(protocol?.name) + okHttpEvent.setError(ioe.message) + okHttpEvent.finishSpan(CONNECT_EVENT) { + it.throwable = ioe + it.status = SpanStatus.INTERNAL_ERROR + } + } + + override fun connectionAcquired(call: Call, connection: Connection) { + originalEventListener?.connectionAcquired(call, connection) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(CONNECTION_EVENT) + } + + override fun connectionReleased(call: Call, connection: Connection) { + originalEventListener?.connectionReleased(call, connection) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(CONNECTION_EVENT) + } + + override fun requestHeadersStart(call: Call) { + originalEventListener?.requestHeadersStart(call) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(REQUEST_HEADERS_EVENT) + } + + override fun requestHeadersEnd(call: Call, request: Request) { + originalEventListener?.requestHeadersEnd(call, request) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(REQUEST_HEADERS_EVENT) + } + + override fun requestBodyStart(call: Call) { + originalEventListener?.requestBodyStart(call) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(REQUEST_BODY_EVENT) + } + + override fun requestBodyEnd(call: Call, byteCount: Long) { + originalEventListener?.requestBodyEnd(call, byteCount) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.finishSpan(REQUEST_BODY_EVENT) { + if (byteCount > 0) { + it.setData("http.request_content_length", byteCount) + } + } + okHttpEvent.setRequestBodySize(byteCount) + } + + override fun requestFailed(call: Call, ioe: IOException) { + originalEventListener?.requestFailed(call, ioe) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setError(ioe.message) + // requestFailed can happen after requestHeaders or requestBody. + // If requestHeaders already finished, we don't change its status. + okHttpEvent.finishSpan(REQUEST_HEADERS_EVENT) { + if (!it.isFinished) { + it.status = SpanStatus.INTERNAL_ERROR + it.throwable = ioe + } + } + okHttpEvent.finishSpan(REQUEST_BODY_EVENT) { + it.status = SpanStatus.INTERNAL_ERROR + it.throwable = ioe + } + } + + override fun responseHeadersStart(call: Call) { + originalEventListener?.responseHeadersStart(call) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(RESPONSE_HEADERS_EVENT) + } + + override fun responseHeadersEnd(call: Call, response: Response) { + originalEventListener?.responseHeadersEnd(call, response) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setResponse(response) + val responseHeadersSpan = okHttpEvent.finishSpan(RESPONSE_HEADERS_EVENT) { + it.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, response.code) + // Let's not override the status of a span that was set + if (it.status == null) { + it.status = SpanStatus.fromHttpStatusCode(response.code) + } + } + okHttpEvent.scheduleFinish(responseHeadersSpan?.finishDate ?: hub.options.dateProvider.now()) + } + + override fun responseBodyStart(call: Call) { + originalEventListener?.responseBodyStart(call) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.startSpan(RESPONSE_BODY_EVENT) + } + + override fun responseBodyEnd(call: Call, byteCount: Long) { + originalEventListener?.responseBodyEnd(call, byteCount) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setResponseBodySize(byteCount) + okHttpEvent.finishSpan(RESPONSE_BODY_EVENT) { + if (byteCount > 0) { + it.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, byteCount) + } + } + } + + override fun responseFailed(call: Call, ioe: IOException) { + originalEventListener?.responseFailed(call, ioe) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setError(ioe.message) + // responseFailed can happen after responseHeaders or responseBody. + // If responseHeaders already finished, we don't change its status. + okHttpEvent.finishSpan(RESPONSE_HEADERS_EVENT) { + if (!it.isFinished) { + it.status = SpanStatus.INTERNAL_ERROR + it.throwable = ioe + } + } + okHttpEvent.finishSpan(RESPONSE_BODY_EVENT) { + it.status = SpanStatus.INTERNAL_ERROR + it.throwable = ioe + } + } + + override fun callEnd(call: Call) { + originalEventListener?.callEnd(call) + val okHttpEvent: SentryOkHttpEvent = eventMap.remove(call) ?: return + okHttpEvent.finishEvent() + } + + override fun callFailed(call: Call, ioe: IOException) { + originalEventListener?.callFailed(call, ioe) + if (!canCreateEventSpan()) { + return + } + val okHttpEvent: SentryOkHttpEvent = eventMap.remove(call) ?: return + okHttpEvent.setError(ioe.message) + okHttpEvent.finishEvent { + it.status = SpanStatus.INTERNAL_ERROR + it.throwable = ioe + } + } + + override fun canceled(call: Call) { + originalEventListener?.canceled(call) + } + + override fun satisfactionFailure(call: Call, response: Response) { + originalEventListener?.satisfactionFailure(call, response) + } + + override fun cacheHit(call: Call, response: Response) { + originalEventListener?.cacheHit(call, response) + } + + override fun cacheMiss(call: Call) { + originalEventListener?.cacheMiss(call) + } + + override fun cacheConditionalHit(call: Call, cachedResponse: Response) { + originalEventListener?.cacheConditionalHit(call, cachedResponse) + } + + private fun canCreateEventSpan(): Boolean { + // If the wrapped EventListener is ours, we shouldn't create spans, as the originalEventListener already did it + return originalEventListener !is SentryOkHttpEventListener + } +} diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt new file mode 100644 index 00000000000..e987d5b588f --- /dev/null +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt @@ -0,0 +1,219 @@ +package io.sentry.okhttp + +import io.sentry.* +import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS +import io.sentry.TypeCheckHint.OKHTTP_REQUEST +import io.sentry.TypeCheckHint.OKHTTP_RESPONSE +import io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback +import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion +import io.sentry.util.Platform +import io.sentry.util.PropagationTargetsUtils +import io.sentry.util.TracingUtils +import io.sentry.util.UrlUtils +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.io.IOException + +/** + * The Sentry's [SentryOkHttpInterceptor], it will automatically add a breadcrumb and start a span + * out of the active span bound to the scope for each HTTP Request. + * If [captureFailedRequests] is enabled, the SDK will capture HTTP Client errors as well. + * + * @param hub The [IHub], internal and only used for testing. + * @param beforeSpan The [ISpan] can be customized or dropped with the [BeforeSpanCallback]. + * @param captureFailedRequests The SDK will only capture HTTP Client errors if it is enabled, + * Defaults to false. + * @param failedRequestStatusCodes The SDK will only capture HTTP Client errors if the HTTP Response + * status code is within the defined ranges. + * @param failedRequestTargets The SDK will only capture HTTP Client errors if the HTTP Request URL + * is a match for any of the defined targets. + */ +public open class SentryOkHttpInterceptor( + private val hub: IHub = HubAdapter.getInstance(), + private val beforeSpan: BeforeSpanCallback? = null, + private val captureFailedRequests: Boolean = true, + private val failedRequestStatusCodes: List = listOf( + HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) + ), + private val failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) +) : Interceptor { + + public constructor() : this(HubAdapter.getInstance()) + public constructor(hub: IHub) : this(hub, null) + public constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) + + init { + addIntegrationToSdkVersion(javaClass) + SentryIntegrationPackageStorage.getInstance() + .addPackage("maven:io.sentry:sentry-okhttp", BuildConfig.VERSION_NAME) + } + + @Suppress("LongMethod") + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + + val urlDetails = UrlUtils.parse(request.url.toString()) + val url = urlDetails.urlOrFallback + val method = request.method + + val span: ISpan? + val okHttpEvent: SentryOkHttpEvent? + + if (SentryOkHttpEventListener.eventMap.containsKey(chain.call())) { + // read the span from the event listener + okHttpEvent = SentryOkHttpEventListener.eventMap[chain.call()] + span = okHttpEvent?.callRootSpan + } else { + // read the span from the bound scope + okHttpEvent = null + val parentSpan = if (Platform.isAndroid()) hub.transaction else hub.span + span = parentSpan?.startChild("http.client", "$method $url") + } + + span?.spanContext?.origin = TRACE_ORIGIN + + urlDetails.applyToSpan(span) + + val isFromEventListener = okHttpEvent != null + var response: Response? = null + var code: Int? = null + + try { + val requestBuilder = request.newBuilder() + + TracingUtils.traceIfAllowed( + hub, + request.url.toString(), + request.headers(BaggageHeader.BAGGAGE_HEADER), + span + )?.let { tracingHeaders -> + requestBuilder.addHeader(tracingHeaders.sentryTraceHeader.name, tracingHeaders.sentryTraceHeader.value) + tracingHeaders.baggageHeader?.let { + requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER) + requestBuilder.addHeader(it.name, it.value) + } + } + + request = requestBuilder.build() + response = chain.proceed(request) + code = response.code + span?.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, code) + span?.status = SpanStatus.fromHttpStatusCode(code) + + // OkHttp errors (4xx, 5xx) don't throw, so it's safe to call within this block. + // breadcrumbs are added on the finally block because we'd like to know if the device + // had an unstable connection or something similar + if (shouldCaptureClientError(request, response)) { + // If we capture the client error directly, it could be associated with the + // currently running span by the backend. In case the listener is in use, that is + // an inner span. So, if the listener is in use, we let it capture the client + // error, to shown it in the http root call span in the dashboard. + if (isFromEventListener && okHttpEvent != null) { + okHttpEvent.setClientErrorResponse(response) + } else { + SentryOkHttpUtils.captureClientError(hub, request, response) + } + } + + return response + } catch (e: IOException) { + span?.apply { + this.throwable = e + this.status = SpanStatus.INTERNAL_ERROR + } + throw e + } finally { + finishSpan(span, request, response, isFromEventListener) + + // The SentryOkHttpEventListener will send the breadcrumb itself if used for this call + if (!isFromEventListener) { + sendBreadcrumb(request, code, response) + } + } + } + + private fun sendBreadcrumb(request: Request, code: Int?, response: Response?) { + val breadcrumb = Breadcrumb.http(request.url.toString(), request.method, code) + request.body?.contentLength().ifHasValidLength { + breadcrumb.setData("http.request_content_length", 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) + } + + hint[OKHTTP_RESPONSE] = it + } + + hub.addBreadcrumb(breadcrumb, hint) + } + + private fun finishSpan(span: ISpan?, request: Request, response: Response?, isFromEventListener: Boolean) { + if (span == null) { + return + } + if (beforeSpan != null) { + val result = beforeSpan.execute(span, request, response) + if (result == null) { + // span is dropped + span.spanContext.sampled = false + } else { + // The SentryOkHttpEventListener will finish the span itself if used for this call + if (!isFromEventListener) { + span.finish() + } + } + } else { + // The SentryOkHttpEventListener will finish the span itself if used for this call + if (!isFromEventListener) { + span.finish() + } + } + } + + private fun Long?.ifHasValidLength(fn: (Long) -> Unit) { + if (this != null && this != -1L) { + fn.invoke(this) + } + } + + private fun shouldCaptureClientError(request: Request, response: Response): Boolean { + // return if the feature is disabled or its not within the range + if (!captureFailedRequests || !containsStatusCode(response.code)) { + return false + } + + // return if its not a target match + if (!PropagationTargetsUtils.contain(failedRequestTargets, request.url.toString())) { + return false + } + + return true + } + + private fun containsStatusCode(statusCode: Int): Boolean { + for (item in failedRequestStatusCodes) { + if (item.isInRange(statusCode)) { + return true + } + } + return false + } + + /** + * The BeforeSpan callback + */ + public fun interface BeforeSpanCallback { + /** + * Mutates or drops span before being added + * + * @param span the span to mutate or drop + * @param request the HTTP request executed by okHttp + * @param response the HTTP response received by okHttp + */ + public fun execute(span: ISpan, request: Request, response: Response?): ISpan? + } +} diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt new file mode 100644 index 00000000000..fbfc689a41a --- /dev/null +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt @@ -0,0 +1,96 @@ +package io.sentry.okhttp + +import io.sentry.Hint +import io.sentry.IHub +import io.sentry.SentryEvent +import io.sentry.TypeCheckHint +import io.sentry.exception.ExceptionMechanismException +import io.sentry.exception.SentryHttpClientException +import io.sentry.protocol.Mechanism +import io.sentry.util.HttpUtils +import io.sentry.util.UrlUtils +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response + +public object SentryOkHttpUtils { + + public fun captureClientError(hub: IHub, request: Request, response: Response) { + // not possible to get a parameterized url, but we remove at least the + // query string and the fragment. + // url example: https://api.github.com/users/getsentry/repos/#fragment?query=query + // url will be: https://api.github.com/users/getsentry/repos/ + // ideally we'd like a parameterized url: https://api.github.com/users/{user}/repos/ + // but that's not possible + val urlDetails = UrlUtils.parse(request.url.toString()) + + val mechanism = Mechanism().apply { + type = "SentryOkHttpInterceptor" + } + val exception = SentryHttpClientException( + "HTTP Client Error with status code: ${response.code}" + ) + val mechanismException = ExceptionMechanismException(mechanism, exception, Thread.currentThread(), true) + val event = SentryEvent(mechanismException) + + val hint = Hint() + hint.set(TypeCheckHint.OKHTTP_REQUEST, request) + hint.set(TypeCheckHint.OKHTTP_RESPONSE, response) + + val sentryRequest = io.sentry.protocol.Request().apply { + urlDetails.applyToRequest(this) + // Cookie is only sent if isSendDefaultPii is enabled + cookies = if (hub.options.isSendDefaultPii) request.headers["Cookie"] else null + method = request.method + headers = getHeaders(hub, request.headers) + + request.body?.contentLength().ifHasValidLength { + bodySize = it + } + } + + val sentryResponse = io.sentry.protocol.Response().apply { + // Set-Cookie is only sent if isSendDefaultPii is enabled due to PII + cookies = if (hub.options.isSendDefaultPii) response.headers["Set-Cookie"] else null + headers = getHeaders(hub, response.headers) + statusCode = response.code + + response.body?.contentLength().ifHasValidLength { + bodySize = it + } + } + + event.request = sentryRequest + event.contexts.setResponse(sentryResponse) + + hub.captureEvent(event, hint) + } + + private fun Long?.ifHasValidLength(fn: (Long) -> Unit) { + if (this != null && this != -1L) { + fn.invoke(this) + } + } + + private fun getHeaders(hub: IHub, requestHeaders: Headers): MutableMap? { + // Headers are only sent if isSendDefaultPii is enabled due to PII + if (!hub.options.isSendDefaultPii) { + return null + } + + val headers = mutableMapOf() + + for (i in 0 until requestHeaders.size) { + val name = requestHeaders.name(i) + + // header is only sent if isn't sensitive + if (HttpUtils.containsSensitiveHeader(name)) { + continue + } + + val value = requestHeaders.value(i) + headers[name] = value + } + return headers + } +} diff --git a/sentry-okhttp/src/main/resources/META-INF/proguard/sentry-okhttp.pro b/sentry-okhttp/src/main/resources/META-INF/proguard/sentry-okhttp.pro new file mode 100644 index 00000000000..3f9ea4feb27 --- /dev/null +++ b/sentry-okhttp/src/main/resources/META-INF/proguard/sentry-okhttp.pro @@ -0,0 +1,13 @@ +##---------------Begin: proguard configuration for OkHttp ---------- + +# To ensure that stack traces is unambiguous +# https://developer.android.com/studio/build/shrink-code#decode-stack-trace +-keepattributes LineNumberTable,SourceFile + +# https://square.github.io/okhttp/features/r8_proguard/ +# If you use OkHttp as a dependency in an Android project which uses R8 as a default compiler you +# don’t have to do anything. The specific rules are already bundled into the JAR which can +# be interpreted by R8 automatically. +# https://raw.githubusercontent.com/square/okhttp/master/okhttp/src/jvmMain/resources/META-INF/proguard/okhttp3.pro + +##---------------End: proguard configuration for OkHttp ---------- diff --git a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventListenerTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventListenerTest.kt similarity index 99% rename from sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventListenerTest.kt rename to sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventListenerTest.kt index a1cea5e3d73..c6d10fce109 100644 --- a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventListenerTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventListenerTest.kt @@ -1,4 +1,4 @@ -package io.sentry.android.okhttp +package io.sentry.okhttp import io.sentry.BaggageHeader import io.sentry.IHub diff --git a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt similarity index 97% rename from sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventTest.kt rename to sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt index 9fab3b475ce..f4ff1a7ee7e 100644 --- a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpEventTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt @@ -1,4 +1,4 @@ -package io.sentry.android.okhttp +package io.sentry.okhttp import io.sentry.Breadcrumb import io.sentry.Hint @@ -15,13 +15,13 @@ import io.sentry.SpanStatus import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext import io.sentry.TypeCheckHint -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT -import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT +import io.sentry.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT import io.sentry.exception.SentryHttpClientException import io.sentry.test.getProperty import okhttp3.Protocol diff --git a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpInterceptorTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt similarity index 99% rename from sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpInterceptorTest.kt rename to sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt index b01ab8edd23..b856d93fb17 100644 --- a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpInterceptorTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt @@ -1,6 +1,6 @@ @file:Suppress("MaxLineLength") -package io.sentry.android.okhttp +package io.sentry.okhttp import io.sentry.BaggageHeader import io.sentry.Breadcrumb diff --git a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpUtilsTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpUtilsTest.kt similarity index 99% rename from sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpUtilsTest.kt rename to sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpUtilsTest.kt index 6f9ea500a7e..ec194543271 100644 --- a/sentry-android-okhttp/src/test/java/io/sentry/android/okhttp/SentryOkHttpUtilsTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpUtilsTest.kt @@ -1,4 +1,4 @@ -package io.sentry.android.okhttp +package io.sentry.okhttp import io.sentry.Hint import io.sentry.IHub diff --git a/sentry-android-okhttp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentry-okhttp/src/test/resources/mockito-extensions/org.mockito.plugin.MockMaker similarity index 100% rename from sentry-android-okhttp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to sentry-okhttp/src/test/resources/mockito-extensions/org.mockito.plugin.MockMaker diff --git a/settings.gradle.kts b/settings.gradle.kts index f87b9e01263..cdec3d71814 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ include( "sentry-opentelemetry:sentry-opentelemetry-agentcustomization", "sentry-opentelemetry:sentry-opentelemetry-agent", "sentry-quartz", + "sentry-okhttp", "sentry-samples:sentry-samples-android", "sentry-samples:sentry-samples-console", "sentry-samples:sentry-samples-jul", From d0bade8f2b46c9c176e6e0c41d9aa0b3eda774a2 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 11:22:37 +0100 Subject: [PATCH 2/7] Use delegation to avoid binary incompatibilities --- .../api/sentry-android-okhttp.api | 41 +++-- .../okhttp/SentryOkHttpEventListener.kt | 141 +++++++++++++++++- .../android/okhttp/SentryOkHttpInterceptor.kt | 29 +--- .../android/okhttp/SentryOkHttpUtils.kt | 16 -- sentry-okhttp/api/sentry-okhttp.api | 9 +- .../io/sentry/okhttp/SentryOkHttpUtils.kt | 4 +- 6 files changed, 183 insertions(+), 57 deletions(-) delete mode 100644 sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt diff --git a/sentry-android-okhttp/api/sentry-android-okhttp.api b/sentry-android-okhttp/api/sentry-android-okhttp.api index 052ccc476f5..a1ad9114a28 100644 --- a/sentry-android-okhttp/api/sentry-android-okhttp.api +++ b/sentry-android-okhttp/api/sentry-android-okhttp.api @@ -6,7 +6,7 @@ public final class io/sentry/android/okhttp/BuildConfig { public fun ()V } -public final class io/sentry/android/okhttp/SentryOkHttpEventListener : io/sentry/okhttp/SentryOkHttpEventListener { +public final class io/sentry/android/okhttp/SentryOkHttpEventListener : okhttp3/EventListener { public fun ()V public fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;)V public synthetic fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -16,24 +16,47 @@ public final class io/sentry/android/okhttp/SentryOkHttpEventListener : io/sentr public synthetic fun (Lio/sentry/IHub;Lokhttp3/EventListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lokhttp3/EventListener$Factory;)V public fun (Lokhttp3/EventListener;)V + public fun cacheConditionalHit (Lokhttp3/Call;Lokhttp3/Response;)V + public fun cacheHit (Lokhttp3/Call;Lokhttp3/Response;)V + public fun cacheMiss (Lokhttp3/Call;)V + public fun callEnd (Lokhttp3/Call;)V + public fun callFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun callStart (Lokhttp3/Call;)V + public fun canceled (Lokhttp3/Call;)V + public fun connectEnd (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;)V + public fun connectFailed (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;Lokhttp3/Protocol;Ljava/io/IOException;)V + public fun connectStart (Lokhttp3/Call;Ljava/net/InetSocketAddress;Ljava/net/Proxy;)V + public fun connectionAcquired (Lokhttp3/Call;Lokhttp3/Connection;)V + public fun connectionReleased (Lokhttp3/Call;Lokhttp3/Connection;)V + public fun dnsEnd (Lokhttp3/Call;Ljava/lang/String;Ljava/util/List;)V + public fun dnsStart (Lokhttp3/Call;Ljava/lang/String;)V + public fun proxySelectEnd (Lokhttp3/Call;Lokhttp3/HttpUrl;Ljava/util/List;)V + public fun proxySelectStart (Lokhttp3/Call;Lokhttp3/HttpUrl;)V + public fun requestBodyEnd (Lokhttp3/Call;J)V + public fun requestBodyStart (Lokhttp3/Call;)V + public fun requestFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun requestHeadersEnd (Lokhttp3/Call;Lokhttp3/Request;)V + public fun requestHeadersStart (Lokhttp3/Call;)V + public fun responseBodyEnd (Lokhttp3/Call;J)V + public fun responseBodyStart (Lokhttp3/Call;)V + public fun responseFailed (Lokhttp3/Call;Ljava/io/IOException;)V + public fun responseHeadersEnd (Lokhttp3/Call;Lokhttp3/Response;)V + public fun responseHeadersStart (Lokhttp3/Call;)V + public fun satisfactionFailure (Lokhttp3/Call;Lokhttp3/Response;)V + public fun secureConnectEnd (Lokhttp3/Call;Lokhttp3/Handshake;)V + public fun secureConnectStart (Lokhttp3/Call;)V } -public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : io/sentry/okhttp/SentryOkHttpInterceptor, okhttp3/Interceptor { +public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor { public fun ()V public fun (Lio/sentry/IHub;)V public fun (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V public synthetic fun (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V - public synthetic fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V + public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; } public abstract interface class io/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback { public abstract fun execute (Lio/sentry/ISpan;Lokhttp3/Request;Lokhttp3/Response;)Lio/sentry/ISpan; } -public final class io/sentry/android/okhttp/SentryOkHttpUtils { - public static final field INSTANCE Lio/sentry/android/okhttp/SentryOkHttpUtils; - public final fun captureClientError (Lio/sentry/IHub;Lokhttp3/Request;Lokhttp3/Response;)V -} - diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt index dfcaa4819d5..d6ffbcefe03 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt @@ -3,7 +3,17 @@ package io.sentry.android.okhttp import io.sentry.HubAdapter import io.sentry.IHub import okhttp3.Call +import okhttp3.Connection import okhttp3.EventListener +import okhttp3.Handshake +import okhttp3.HttpUrl +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Proxy /** * Logs network performance event metrics to Sentry @@ -33,7 +43,7 @@ import okhttp3.EventListener class SentryOkHttpEventListener( hub: IHub = HubAdapter.getInstance(), originalEventListenerCreator: ((call: Call) -> EventListener)? = null -) : io.sentry.okhttp.SentryOkHttpEventListener(hub, originalEventListenerCreator) { +) : EventListener() { constructor() : this( HubAdapter.getInstance(), originalEventListenerCreator = null @@ -58,4 +68,133 @@ class SentryOkHttpEventListener( hub, originalEventListenerCreator = { originalEventListenerFactory.create(it) } ) + + private val delegate = io.sentry.okhttp.SentryOkHttpEventListener(hub, originalEventListenerCreator) + + override fun cacheConditionalHit(call: Call, cachedResponse: Response) { + delegate.cacheConditionalHit(call, cachedResponse) + } + + override fun cacheHit(call: Call, response: Response) { + delegate.cacheHit(call, response) + } + + override fun cacheMiss(call: Call) { + delegate.cacheMiss(call) + } + + override fun callEnd(call: Call) { + delegate.callEnd(call) + } + + override fun callFailed(call: Call, ioe: IOException) { + delegate.callFailed(call, ioe) + } + + override fun callStart(call: Call) { + delegate.callStart(call) + } + + override fun canceled(call: Call) { + delegate.canceled(call) + } + + override fun connectEnd( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol? + ) { + delegate.connectEnd(call, inetSocketAddress, proxy, protocol) + } + + override fun connectFailed( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol?, + ioe: IOException + ) { + delegate.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) + } + + override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) { + delegate.connectStart(call, inetSocketAddress, proxy) + } + + override fun connectionAcquired(call: Call, connection: Connection) { + delegate.connectionAcquired(call, connection) + } + + override fun connectionReleased(call: Call, connection: Connection) { + delegate.connectionReleased(call, connection) + } + + override fun dnsEnd(call: Call, domainName: String, inetAddressList: List) { + delegate.dnsEnd(call, domainName, inetAddressList) + } + + override fun dnsStart(call: Call, domainName: String) { + delegate.dnsStart(call, domainName) + } + + override fun proxySelectEnd(call: Call, url: HttpUrl, proxies: List) { + delegate.proxySelectEnd(call, url, proxies) + } + + override fun proxySelectStart(call: Call, url: HttpUrl) { + delegate.proxySelectStart(call, url) + } + + override fun requestBodyEnd(call: Call, byteCount: Long) { + delegate.requestBodyEnd(call, byteCount) + } + + override fun requestBodyStart(call: Call) { + delegate.requestBodyStart(call) + } + + override fun requestFailed(call: Call, ioe: IOException) { + delegate.requestFailed(call, ioe) + } + + override fun requestHeadersEnd(call: Call, request: Request) { + delegate.requestHeadersEnd(call, request) + } + + override fun requestHeadersStart(call: Call) { + delegate.requestHeadersStart(call) + } + + override fun responseBodyEnd(call: Call, byteCount: Long) { + delegate.responseBodyEnd(call, byteCount) + } + + override fun responseBodyStart(call: Call) { + delegate.responseBodyStart(call) + } + + override fun responseFailed(call: Call, ioe: IOException) { + delegate.responseFailed(call, ioe) + } + + override fun responseHeadersEnd(call: Call, response: Response) { + delegate.responseHeadersEnd(call, response) + } + + override fun responseHeadersStart(call: Call) { + delegate.responseHeadersStart(call) + } + + override fun satisfactionFailure(call: Call, response: Response) { + delegate.satisfactionFailure(call, response) + } + + override fun secureConnectEnd(call: Call, handshake: Handshake?) { + delegate.secureConnectEnd(call, handshake) + } + + override fun secureConnectStart(call: Call) { + delegate.secureConnectStart(call) + } } diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt index e4d6ff1e7b2..678434f1db0 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt @@ -32,40 +32,25 @@ import okhttp3.Response ) class SentryOkHttpInterceptor( private val hub: IHub = HubAdapter.getInstance(), - private val beforeSpan: io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback? = null, + private val beforeSpan: BeforeSpanCallback? = null, private val captureFailedRequests: Boolean = true, private val failedRequestStatusCodes: List = listOf( HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) ), private val failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) -) : io.sentry.okhttp.SentryOkHttpInterceptor( +) : Interceptor by io.sentry.okhttp.SentryOkHttpInterceptor( hub, - beforeSpan, + { span, request, response -> + beforeSpan?.execute(span, request, response) + }, captureFailedRequests, failedRequestStatusCodes, failedRequestTargets -), Interceptor { +) { constructor() : this(HubAdapter.getInstance()) - constructor(hub: IHub) : this(hub, null as io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback?) + constructor(hub: IHub) : this(hub, null) constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) - constructor( - hub: IHub, - beforeSpan: BeforeSpanCallback? = null, - captureFailedRequests: Boolean = false, - failedRequestStatusCodes: List = listOf( - HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) - ), - failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) - ) : this( - hub, - io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback { span, request, response -> - beforeSpan?.execute(span, request, response) - }, - captureFailedRequests, - failedRequestStatusCodes, - failedRequestTargets - ) init { addIntegrationToSdkVersion(javaClass) diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt deleted file mode 100644 index 390e70aaf8e..00000000000 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpUtils.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.sentry.android.okhttp - -import io.sentry.IHub -import okhttp3.Request -import okhttp3.Response - -@Deprecated( - "Use SentryOkHttpUtils from sentry-okhttp instead", - ReplaceWith("SentryOkHttpUtils", "io.sentry.okhttp.SentryOkHttpUtils") -) -object SentryOkHttpUtils { - - fun captureClientError(hub: IHub, request: Request, response: Response) { - io.sentry.okhttp.SentryOkHttpUtils.captureClientError(hub, request, response) - } -} diff --git a/sentry-okhttp/api/sentry-okhttp.api b/sentry-okhttp/api/sentry-okhttp.api index 02cf0581151..3095659c88d 100644 --- a/sentry-okhttp/api/sentry-okhttp.api +++ b/sentry-okhttp/api/sentry-okhttp.api @@ -3,7 +3,7 @@ public final class io/sentry/okhttp/BuildConfig { public static final field VERSION_NAME Ljava/lang/String; } -public final class io/sentry/okhttp/SentryOkHttpEventListener : okhttp3/EventListener { +public class io/sentry/okhttp/SentryOkHttpEventListener : okhttp3/EventListener { public static final field Companion Lio/sentry/okhttp/SentryOkHttpEventListener$Companion; public fun ()V public fun (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;)V @@ -48,7 +48,7 @@ public final class io/sentry/okhttp/SentryOkHttpEventListener : okhttp3/EventLis public final class io/sentry/okhttp/SentryOkHttpEventListener$Companion { } -public final class io/sentry/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor { +public class io/sentry/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor { public fun ()V public fun (Lio/sentry/IHub;)V public fun (Lio/sentry/IHub;Lio/sentry/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V @@ -61,8 +61,3 @@ public abstract interface class io/sentry/okhttp/SentryOkHttpInterceptor$BeforeS public abstract fun execute (Lio/sentry/ISpan;Lokhttp3/Request;Lokhttp3/Response;)Lio/sentry/ISpan; } -public final class io/sentry/okhttp/SentryOkHttpUtils { - public static final field INSTANCE Lio/sentry/okhttp/SentryOkHttpUtils; - public final fun captureClientError (Lio/sentry/IHub;Lokhttp3/Request;Lokhttp3/Response;)V -} - diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt index fbfc689a41a..0cfc1c5a755 100644 --- a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpUtils.kt @@ -13,9 +13,9 @@ import okhttp3.Headers import okhttp3.Request import okhttp3.Response -public object SentryOkHttpUtils { +internal object SentryOkHttpUtils { - public fun captureClientError(hub: IHub, request: Request, response: Response) { + internal fun captureClientError(hub: IHub, request: Request, response: Response) { // not possible to get a parameterized url, but we remove at least the // query string and the fragment. // url example: https://api.github.com/users/getsentry/repos/#fragment?query=query From 270ff53f6ed8bb3541ea05c2c75b26eae0d1ea6a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 11:39:54 +0100 Subject: [PATCH 3/7] Changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0463aba4dcf..6100ea0dbf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # Changelog -## Unrelease +## Unreleased ### Features - Add `sentry-okhttp` module to support instrumenting OkHttp in non-Android projects + - This deprecates `sentry-android-okhttp` classes. Make sure to replace `io.sentry.android.okhttp` package name with `io.sentry.okhttp` before the next major, where the classes will be removed + - `SentryOkHttpUtils` was removed from public API as it's been exposed by mistake ## 7.0.0-rc.1 From c09afd569ff8ce9e64c01475afb225725e67a367 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 11:46:43 +0100 Subject: [PATCH 4/7] Formatting --- .../java/io/sentry/okhttp/SentryOkHttpInterceptor.kt | 11 ++++++++++- .../java/io/sentry/okhttp/SentryOkHttpEventTest.kt | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt index e987d5b588f..1f1aaf8c4e4 100644 --- a/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt +++ b/sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt @@ -1,7 +1,16 @@ package io.sentry.okhttp -import io.sentry.* +import io.sentry.BaggageHeader +import io.sentry.Breadcrumb +import io.sentry.Hint +import io.sentry.HttpStatusCodeRange +import io.sentry.HubAdapter +import io.sentry.IHub +import io.sentry.ISpan +import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS +import io.sentry.SpanDataConvention +import io.sentry.SpanStatus import io.sentry.TypeCheckHint.OKHTTP_REQUEST import io.sentry.TypeCheckHint.OKHTTP_RESPONSE import io.sentry.okhttp.SentryOkHttpInterceptor.BeforeSpanCallback diff --git a/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt index f4ff1a7ee7e..1363c237850 100644 --- a/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt +++ b/sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt @@ -15,6 +15,7 @@ import io.sentry.SpanStatus import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext import io.sentry.TypeCheckHint +import io.sentry.exception.SentryHttpClientException import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT @@ -22,7 +23,6 @@ import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVEN import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT import io.sentry.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT -import io.sentry.exception.SentryHttpClientException import io.sentry.test.getProperty import okhttp3.Protocol import okhttp3.Request From 6e6f7178203ecf603cb8e913fccd04aee3e78860 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 13:29:04 +0100 Subject: [PATCH 5/7] Update sample to use new package name --- .../src/main/java/io/sentry/samples/android/GithubAPI.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GithubAPI.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GithubAPI.kt index 09ad9a2d07d..66e455a190e 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GithubAPI.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GithubAPI.kt @@ -1,8 +1,8 @@ package io.sentry.samples.android import io.sentry.HttpStatusCodeRange -import io.sentry.android.okhttp.SentryOkHttpEventListener -import io.sentry.android.okhttp.SentryOkHttpInterceptor +import io.sentry.okhttp.SentryOkHttpEventListener +import io.sentry.okhttp.SentryOkHttpInterceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory From 1d7755502bcbd7ea3c43fb31b0d04c7703937929 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 13:53:50 +0100 Subject: [PATCH 6/7] pr id --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6100ea0dbf7..00d3494b154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- Add `sentry-okhttp` module to support instrumenting OkHttp in non-Android projects +- Add `sentry-okhttp` module to support instrumenting OkHttp in non-Android projects ([#3005](https://github.com/getsentry/sentry-java/pull/3005)) - This deprecates `sentry-android-okhttp` classes. Make sure to replace `io.sentry.android.okhttp` package name with `io.sentry.okhttp` before the next major, where the classes will be removed - `SentryOkHttpUtils` was removed from public API as it's been exposed by mistake From 3143eed144354ab4064822b3ecebc09f28af5c00 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Nov 2023 14:20:08 +0100 Subject: [PATCH 7/7] Suppress detekt --- .../java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt index d6ffbcefe03..7ca5313d8f6 100644 --- a/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt +++ b/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEventListener.kt @@ -40,6 +40,7 @@ import java.net.Proxy "Use SentryOkHttpEventListener from sentry-okhttp instead", ReplaceWith("SentryOkHttpEventListener", "io.sentry.okhttp.SentryOkHttpEventListener") ) +@Suppress("TooManyFunctions") class SentryOkHttpEventListener( hub: IHub = HubAdapter.getInstance(), originalEventListenerCreator: ((call: Call) -> EventListener)? = null