Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OkHttp event spans #2659

Merged
merged 14 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,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)
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
}

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