Skip to content

Commit

Permalink
Feat: OkHttp callback for Customising the Span (#1478)
Browse files Browse the repository at this point in the history
Fixes #1365
  • Loading branch information
maciejwalkowiak committed May 25, 2021
1 parent 248e549 commit 5b6d1ea
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased

* Feat: OkHttp callback for Customising the Span (#1478)
* Feat: Add breadcrumb in Spring RestTemplate integration (#1481)
* Fix: Cloning Stack (#1483)

Expand Down
8 changes: 6 additions & 2 deletions sentry-android-okhttp/api/sentry-android-okhttp.api
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ public final class io/sentry/android/okhttp/BuildConfig {

public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor {
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;)V
public synthetic fun <init> (Lio/sentry/IHub;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V
public synthetic fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)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;
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package io.sentry.android.okhttp
import io.sentry.Breadcrumb
import io.sentry.HubAdapter
import io.sentry.IHub
import io.sentry.ISpan
import io.sentry.SpanStatus
import java.io.IOException
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

class SentryOkHttpInterceptor(
private val hub: IHub = HubAdapter.getInstance()
private val hub: IHub = HubAdapter.getInstance(),
private val beforeSpan: BeforeSpanCallback? = null
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
Expand All @@ -19,7 +22,7 @@ class SentryOkHttpInterceptor(
val method = request.method

// read transaction from the bound scope
val span = hub.span?.startChild("http.client", "$method $url")
var span = hub.span?.startChild("http.client", "$method $url")

var response: Response? = null

Expand All @@ -39,7 +42,12 @@ class SentryOkHttpInterceptor(
}
throw e
} finally {
span?.finish()
if (span != null) {
if (beforeSpan != null) {
span = beforeSpan.execute(span, request, response)
}
span?.finish()
}
val breadcrumb = Breadcrumb.http(request.url.toString(), request.method, code)
request.body?.contentLength().ifHasValidLength {
breadcrumb.setData("requestBodySize", it)
Expand All @@ -56,4 +64,18 @@ class SentryOkHttpInterceptor(
fn.invoke(this)
}
}

/**
* The BeforeSpan callback
*/
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
*/
fun execute(span: ISpan, request: Request, response: Response?): ISpan?
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.Breadcrumb
import io.sentry.IHub
import io.sentry.ISpan
import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
import io.sentry.SentryTracer
Expand All @@ -15,6 +16,7 @@ import io.sentry.TransactionContext
import java.io.IOException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
Expand All @@ -24,6 +26,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.SocketPolicy
Expand All @@ -32,7 +35,7 @@ class SentryOkHttpInterceptorTest {

class Fixture {
val hub = mock<IHub>()
val interceptor = SentryOkHttpInterceptor(hub)
var interceptor = SentryOkHttpInterceptor(hub)
val server = MockWebServer()
val sentryTracer = SentryTracer(TransactionContext("name", "op"), hub)

Expand All @@ -44,7 +47,8 @@ class SentryOkHttpInterceptorTest {
isSpanActive: Boolean = true,
httpStatusCode: Int = 201,
responseBody: String = "success",
socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN
socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN,
beforeSpan: SentryOkHttpInterceptor.BeforeSpanCallback? = null
): OkHttpClient {
if (isSpanActive) {
whenever(hub.span).thenReturn(sentryTracer)
Expand All @@ -54,6 +58,9 @@ class SentryOkHttpInterceptorTest {
.setSocketPolicy(socketPolicy)
.setResponseCode(httpStatusCode))
server.start()
if (beforeSpan != null) {
interceptor = SentryOkHttpInterceptor(hub, beforeSpan)
}
return OkHttpClient.Builder().addInterceptor(interceptor).build()
}
}
Expand Down Expand Up @@ -98,6 +105,7 @@ class SentryOkHttpInterceptorTest {
assertEquals("http.client", httpClientSpan.operation)
assertEquals("GET ${request.url}", httpClientSpan.description)
assertEquals(SpanStatus.OK, httpClientSpan.status)
assertTrue(httpClientSpan.isFinished)
}

@Test
Expand Down Expand Up @@ -159,4 +167,46 @@ class SentryOkHttpInterceptorTest {
assertEquals(SpanStatus.INTERNAL_ERROR, httpClientSpan.status)
assertTrue(httpClientSpan.throwable is IOException)
}

@Test
fun `customizer modifies span`() {
val sut = fixture.getSut(beforeSpan = object : SentryOkHttpInterceptor.BeforeSpanCallback {
override fun execute(span: ISpan, request: Request, response: Response?): ISpan {
span.description = "overwritten description"
return span
}
})
val request = getRequest()
sut.newCall(request).execute()
assertEquals(1, fixture.sentryTracer.children.size)
val httpClientSpan = fixture.sentryTracer.children.first()
assertEquals("overwritten description", httpClientSpan.description)
}

@Test
fun `customizer receives request and response`() {
var request: Request? = null
val sut = fixture.getSut(beforeSpan = object : SentryOkHttpInterceptor.BeforeSpanCallback {
override fun execute(span: ISpan, req: Request, res: Response?): ISpan {
assertEquals(request!!.url, req.url)
assertEquals(request!!.method, req.method)
assertNotNull(res) {
assertEquals(201, it.code)
}
return span
} })
request = getRequest()
sut.newCall(request).execute()
}

@Test
fun `customizer can drop the span`() {
val sut = fixture.getSut(beforeSpan = object : SentryOkHttpInterceptor.BeforeSpanCallback {
override fun execute(span: ISpan, request: Request, response: Response?): ISpan? {
return null
} })
sut.newCall(getRequest()).execute()
val httpClientSpan = fixture.sentryTracer.children.first()
assertFalse(httpClientSpan.isFinished)
}
}

0 comments on commit 5b6d1ea

Please sign in to comment.