Skip to content

Commit

Permalink
Add OkHttp event spans (#2659)
Browse files Browse the repository at this point in the history
* made SentryOkHttpInterceptor work nicely with SentryOkHttpEventListener
* added SentryOkHttpEventListener, with an optional instance of EventListener to propagate calls to
* added SentryOkHttpEvent
* updated span data to align with https://develop.sentry.dev/sdk/performance/span-data-conventions/
  • Loading branch information
stefanosiano committed May 26, 2023
1 parent 37cd75a commit fe90ed9
Show file tree
Hide file tree
Showing 10 changed files with 1,447 additions and 32 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@

### Features

- More granular http requests instrumentation with a new SentryOkHttpEventListener ([#2659](https://github.com/getsentry/sentry-java/pull/2659))
- Create spans for time spent on:
- Proxy selection
- DNS resolution
- HTTPS setup
- Connection
- Requesting headers
- Receiving response
- You can attach the event listener to your OkHttpClient through `client.eventListener(new SentryOkHttpEventListener()).addInterceptor(new SentryOkHttpInterceptor()).build();`
- In case you already have an event listener you can use the SentryOkHttpEventListener as well through `client.eventListener(new SentryOkHttpEventListener(myListener)).addInterceptor(new SentryOkHttpInterceptor()).build();`
- Add Screenshot and ViewHierarchy to integrations list ([#2698](https://github.com/getsentry/sentry-java/pull/2698))
- New ANR detection based on [ApplicationExitInfo API](https://developer.android.com/reference/android/app/ApplicationExitInfo) ([#2697](https://github.com/getsentry/sentry-java/pull/2697))
- This implementation completely replaces the old one (based on a watchdog) on devices running Android 11 and above:
Expand Down
40 changes: 40 additions & 0 deletions sentry-android-okhttp/api/sentry-android-okhttp.api
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@ public final class io/sentry/android/okhttp/BuildConfig {
public fun <init> ()V
}

public final class io/sentry/android/okhttp/SentryOkHttpEventListener : okhttp3/EventListener {
public static final field Companion Lio/sentry/android/okhttp/SentryOkHttpEventListener$Companion;
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lio/sentry/IHub;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/IHub;Lokhttp3/EventListener$Factory;)V
public synthetic fun <init> (Lio/sentry/IHub;Lokhttp3/EventListener$Factory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/IHub;Lokhttp3/EventListener;)V
public synthetic fun <init> (Lio/sentry/IHub;Lokhttp3/EventListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lokhttp3/EventListener$Factory;)V
public fun <init> (Lokhttp3/EventListener;)V
public fun callEnd (Lokhttp3/Call;)V
public fun callFailed (Lokhttp3/Call;Ljava/io/IOException;)V
public fun callStart (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 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 : io/sentry/IntegrationName, okhttp3/Interceptor {
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;)V
Expand Down
1 change: 1 addition & 0 deletions sentry-android-okhttp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ dependencies {
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))

// tests
testImplementation(projects.sentryTestSupport)
testImplementation(Config.Libs.okhttp)
testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.androidxJunit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package io.sentry.android.okhttp

import io.sentry.Breadcrumb
import io.sentry.Hint
import io.sentry.IHub
import io.sentry.ISpan
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.util.UrlUtils
import okhttp3.Request
import okhttp3.Response
import java.util.concurrent.ConcurrentHashMap

private const val PROTOCOL_KEY = "protocol"
private const val ERROR_MESSAGE_KEY = "error_message"

internal class SentryOkHttpEvent(private val hub: IHub, private val request: Request) {
private val eventSpans: MutableMap<String, ISpan> = ConcurrentHashMap()
private val breadcrumb: Breadcrumb
internal val callRootSpan: ISpan?
private var response: Response? = null

init {
val urlDetails = UrlUtils.parse(request.url.toString())
val url = urlDetails.urlOrFallback
val host: String = request.url.host
val encodedPath: String = request.url.encodedPath
val method: String = request.method

// We start the call span that will contain all the others
callRootSpan = hub.span?.startChild("http.client", "$method $url")

urlDetails.applyToSpan(callRootSpan)

// We setup a breadcrumb with all meaningful data
breadcrumb = Breadcrumb.http(url, method)
breadcrumb.setData("host", host)
breadcrumb.setData("path", encodedPath)

// We add the same data to the root call span
callRootSpan?.setData("url", url)
callRootSpan?.setData("host", host)
callRootSpan?.setData("path", encodedPath)
callRootSpan?.setData("http.method", method)
}

/**
* Sets the [Response] that will be sent in the breadcrumb [Hint].
* Also, it sets the protocol and status code in the breadcrumb and the call root span.
*/
fun setResponse(response: Response) {
this.response = response
breadcrumb.setData(PROTOCOL_KEY, response.protocol.name)
breadcrumb.setData("status_code", response.code)
callRootSpan?.setData(PROTOCOL_KEY, response.protocol.name)
callRootSpan?.setData("http.status_code", response.code)
callRootSpan?.status = SpanStatus.fromHttpStatusCode(response.code)
}

fun setProtocol(protocolName: String?) {
if (protocolName != null) {
breadcrumb.setData(PROTOCOL_KEY, protocolName)
callRootSpan?.setData(PROTOCOL_KEY, protocolName)
}
}

fun setRequestBodySize(byteCount: Long) {
if (byteCount > -1) {
breadcrumb.setData("request_content_length", byteCount)
callRootSpan?.setData("http.request_content_length", byteCount)
}
}

fun setResponseBodySize(byteCount: Long) {
if (byteCount > -1) {
breadcrumb.setData("response_content_length", byteCount)
callRootSpan?.setData("http.response_content_length", byteCount)
}
}

/** Sets the [errorMessage] if not null. */
fun setError(errorMessage: String?) {
if (errorMessage != null) {
breadcrumb.setData(ERROR_MESSAGE_KEY, errorMessage)
callRootSpan?.setData(ERROR_MESSAGE_KEY, errorMessage)
}
}

/** Starts a span, if the callRootSpan is not null. */
fun startSpan(event: String) {
// Find the parent of the span being created. E.g. secureConnect is child of connect
val parentSpan = when (event) {
// PROXY_SELECT, DNS, CONNECT and CONNECTION are not children of one another
SECURE_CONNECT_EVENT -> eventSpans[CONNECT_EVENT]
REQUEST_HEADERS_EVENT -> eventSpans[CONNECTION_EVENT]
REQUEST_BODY_EVENT -> eventSpans[CONNECTION_EVENT]
RESPONSE_HEADERS_EVENT -> eventSpans[CONNECTION_EVENT]
RESPONSE_BODY_EVENT -> eventSpans[CONNECTION_EVENT]
else -> callRootSpan
} ?: callRootSpan
val span = parentSpan?.startChild("http.client.$event") ?: return
eventSpans[event] = span
}

/** Finishes a previously started span, and runs [beforeFinish] on it and on the call root span. */
fun finishSpan(event: String, beforeFinish: ((span: ISpan) -> Unit)? = null) {
val span = eventSpans[event] ?: return
beforeFinish?.invoke(span)
callRootSpan?.let { beforeFinish?.invoke(it) }
span.finish()
}

/** Finishes the call root span, and runs [beforeFinish] on it. Then a breadcrumb is sent. */
fun finishEvent(beforeFinish: ((span: ISpan) -> Unit)? = null) {
callRootSpan ?: return

// We forcefully finish all spans, even if they should already have been finished through finishSpan()
eventSpans.values.filter { !it.isFinished }.forEach { it.finish(SpanStatus.DEADLINE_EXCEEDED) }
beforeFinish?.invoke(callRootSpan)
callRootSpan.finish()

// We put data in the hint and send a breadcrumb
val hint = Hint()
hint.set(TypeCheckHint.OKHTTP_REQUEST, request)
response?.let { hint.set(TypeCheckHint.OKHTTP_RESPONSE, it) }

hub.addBreadcrumb(breadcrumb, hint)
return
}
}
Loading

0 comments on commit fe90ed9

Please sign in to comment.