Skip to content

Commit

Permalink
Feat: Add tracestate HTTP header support. (#1683)
Browse files Browse the repository at this point in the history
Add experimental tracestate HTTP header support defined in https://develop.sentry.dev/sdk/performance/trace-context/#client-options.

Trace context is added to each envelope containing transaction or an event.

In addition to that, SentryOkHttpInterceptor attaches a new tracestate header to all outgoing requests.

By default this feature is off and is controller by a traceSampling flag on SentryOptions. All new API classes or methods are marked as @experimental.
  • Loading branch information
maciejwalkowiak authored Sep 2, 2021
1 parent 90a6821 commit 8dcd504
Show file tree
Hide file tree
Showing 38 changed files with 1,532 additions and 101 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Feat: Add tracestate HTTP header support (#1683)

Breaking changes:

* Updated proguard keep rule for enums, which affects consumer application code (#1694)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.sentry.SentryLevel;
import io.sentry.util.Objects;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -50,6 +51,8 @@ final class ManifestMetadataReader {
static final String TRACES_ACTIVITY_AUTO_FINISH_ENABLE =
"io.sentry.traces.activity.auto-finish.enable";

@ApiStatus.Experimental static final String TRACE_SAMPLING = "io.sentry.traces.trace-sampling";

static final String ATTACH_THREADS = "io.sentry.attach-threads";

/** ManifestMetadataReader ctor */
Expand Down Expand Up @@ -184,6 +187,9 @@ static void applyMetadata(
}
}

options.setTraceSampling(
readBool(metadata, logger, TRACE_SAMPLING, options.isTraceSampling()));

options.setEnableAutoActivityLifecycleTracing(
readBool(
metadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class ActivityLifecycleIntegrationTest {
private class Fixture {
val application = mock<Application>()
val hub = mock<Hub>()
val options = SentryAndroidOptions()
val options = SentryAndroidOptions().apply {
dsn = "https://key@sentry.io/proj"
}
val bundle = mock<Bundle>()
val context = TransactionContext("name", "op")
val activityFramesTracker = mock<ActivityFramesTracker>()
Expand Down Expand Up @@ -343,7 +345,7 @@ class ActivityLifecycleIntegrationTest {

verify(fixture.hub).captureTransaction(check {
assertEquals(SpanStatus.OK, it.status)
})
}, anyOrNull())
}

@Test
Expand All @@ -361,7 +363,7 @@ class ActivityLifecycleIntegrationTest {

verify(fixture.hub).captureTransaction(check {
assertEquals(SpanStatus.UNKNOWN_ERROR, it.status)
})
}, anyOrNull())
}

@Test
Expand All @@ -375,7 +377,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityPostResumed(activity)

verify(fixture.hub, never()).captureTransaction(any())
verify(fixture.hub, never()).captureTransaction(any(), anyOrNull())
}

@Test
Expand All @@ -386,7 +388,7 @@ class ActivityLifecycleIntegrationTest {
val activity = mock<Activity>()
sut.onActivityPostResumed(activity)

verify(fixture.hub, never()).captureTransaction(any())
verify(fixture.hub, never()).captureTransaction(any(), anyOrNull())
}

@Test
Expand All @@ -399,7 +401,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityDestroyed(activity)

verify(fixture.hub).captureTransaction(any())
verify(fixture.hub).captureTransaction(any(), anyOrNull())
}

@Test
Expand Down Expand Up @@ -436,7 +438,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(mock(), mock())

sut.onActivityCreated(mock(), fixture.bundle)
verify(fixture.hub).captureTransaction(any())
verify(fixture.hub).captureTransaction(any(), anyOrNull())
}

@Test
Expand All @@ -449,7 +451,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, mock())
sut.onActivityResumed(activity)

verify(fixture.hub, never()).captureTransaction(any())
verify(fixture.hub, never()).captureTransaction(any(), any())
}

@Test
Expand All @@ -476,7 +478,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, mock())
sut.onActivityResumed(activity)

verify(fixture.hub).captureTransaction(any())
verify(fixture.hub).captureTransaction(any(), anyOrNull())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -663,4 +663,29 @@ class ManifestMetadataReaderTest {
// Assert
assertTrue(fixture.options.isEnableActivityLifecycleTracingAutoFinish)
}

@Test
fun `applyMetadata reads tracSampling to options`() {
// Arrange
val bundle = bundleOf(ManifestMetadataReader.TRACE_SAMPLING to true)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options)

// Assert
assertTrue(fixture.options.isTraceSampling)
}

@Test
fun `applyMetadata reads traceSampling to options and keeps default`() {
// Arrange
val context = fixture.getContext()

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options)

// Assert
assertFalse(fixture.options.isTraceSampling)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ class SentryOkHttpInterceptor(

var code: Int? = null
try {
val requestBuilder = request.newBuilder()
span?.toSentryTrace()?.let {
request = request.newBuilder().addHeader(it.name, it.value).build()
requestBuilder.addHeader(it.name, it.value)
}
span?.toTraceStateHeader()?.let {
requestBuilder.addHeader(it.name, it.value)
}
request = requestBuilder.build()
response = chain.proceed(request)
code = response.code
span?.status = SpanStatus.fromHttpStatusCode(code)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
import io.sentry.SentryTracer
import io.sentry.SpanStatus
import io.sentry.TraceStateHeader
import io.sentry.TransactionContext
import java.io.IOException
import kotlin.test.Test
Expand Down Expand Up @@ -40,7 +41,10 @@ class SentryOkHttpInterceptorTest {
val sentryTracer = SentryTracer(TransactionContext("name", "op"), hub)

init {
whenever(hub.options).thenReturn(SentryOptions())
whenever(hub.options).thenReturn(SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
isTraceSampling = true
})
}

fun getSut(
Expand Down Expand Up @@ -73,11 +77,12 @@ class SentryOkHttpInterceptorTest {
.toMediaType())).url(fixture.server.url("/hello")).build() }

@Test
fun `when there is an active span, adds sentry trace header to the request`() {
fun `when there is an active span, adds sentry trace headers to the request`() {
val sut = fixture.getSut()
sut.newCall(getRequest()).execute()
val recorderRequest = fixture.server.takeRequest()
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNotNull(recorderRequest.headers[TraceStateHeader.TRACE_STATE_HEADER])
}

@Test
Expand All @@ -86,6 +91,7 @@ class SentryOkHttpInterceptorTest {
sut.newCall(getRequest()).execute()
val recorderRequest = fixture.server.takeRequest()
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNull(recorderRequest.headers[TraceStateHeader.TRACE_STATE_HEADER])
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.spring.tracing

import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.check
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
Expand Down Expand Up @@ -37,7 +38,9 @@ class SentryTracingFilterTest {
val transactionNameProvider = spy(TransactionNameProvider())

init {
whenever(hub.options).thenReturn(SentryOptions())
whenever(hub.options).thenReturn(SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
})
}

fun getSut(isEnabled: Boolean = true, status: Int = 200, sentryTraceHeader: String? = null): SentryTracingFilter {
Expand Down Expand Up @@ -72,7 +75,7 @@ class SentryTracingFilterTest {
assertThat(it.transaction).isEqualTo("POST /product/{id}")
assertThat(it.contexts.trace!!.status).isEqualTo(SpanStatus.OK)
assertThat(it.contexts.trace!!.operation).isEqualTo("http.server")
})
}, anyOrNull())
}

@Test
Expand All @@ -83,7 +86,7 @@ class SentryTracingFilterTest {

verify(fixture.hub).captureTransaction(check {
assertThat(it.contexts.trace!!.status).isEqualTo(SpanStatus.INTERNAL_ERROR)
})
}, anyOrNull())
}

@Test
Expand All @@ -94,7 +97,7 @@ class SentryTracingFilterTest {

verify(fixture.hub).captureTransaction(check {
assertThat(it.contexts.trace!!.status).isNull()
})
}, anyOrNull())
}

@Test
Expand All @@ -105,7 +108,7 @@ class SentryTracingFilterTest {

verify(fixture.hub).captureTransaction(check {
assertThat(it.contexts.trace!!.parentSpanId).isNull()
})
}, anyOrNull())
}

@Test
Expand All @@ -117,7 +120,7 @@ class SentryTracingFilterTest {

verify(fixture.hub).captureTransaction(check {
assertThat(it.contexts.trace!!.parentSpanId).isEqualTo(parentSpanId)
})
}, anyOrNull())
}

@Test
Expand Down Expand Up @@ -145,6 +148,6 @@ class SentryTracingFilterTest {
}
verify(fixture.hub).captureTransaction(check {
assertThat(it.status).isEqualTo(SpanStatus.INTERNAL_ERROR)
})
}, anyOrNull())
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.spring.tracing

import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.check
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
Expand All @@ -9,9 +10,8 @@ import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.IHub
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.SentryOptions
import io.sentry.SentryTracer
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -46,7 +46,10 @@ class SentryTransactionAdviceTest {
@BeforeTest
fun setup() {
reset(hub)
whenever(hub.startTransaction(any<String>(), any(), eq(true))).thenAnswer { io.sentry.SentryTracer(TransactionContext(it.arguments[0] as String, it.arguments[1] as String), hub) }
whenever(hub.startTransaction(any<String>(), any(), eq(true))).thenAnswer { SentryTracer(TransactionContext(it.arguments[0] as String, it.arguments[1] as String), hub) }
whenever(hub.options).thenReturn(SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
})
}

@Test
Expand All @@ -56,15 +59,15 @@ class SentryTransactionAdviceTest {
assertThat(it.transaction).isEqualTo("customName")
assertThat(it.contexts.trace!!.operation).isEqualTo("bean")
assertThat(it.status).isEqualTo(SpanStatus.OK)
})
}, anyOrNull())
}

@Test
fun `when method annotated with @SentryTransaction throws exception, sets error status on transaction`() {
assertThrows<RuntimeException> { sampleService.methodThrowingException() }
verify(hub).captureTransaction(check {
assertThat(it.status).isEqualTo(SpanStatus.INTERNAL_ERROR)
})
}, anyOrNull())
}

@Test
Expand All @@ -73,19 +76,15 @@ class SentryTransactionAdviceTest {
verify(hub).captureTransaction(check {
assertThat(it.transaction).isEqualTo("SampleService.methodWithoutTransactionNameSet")
assertThat(it.contexts.trace!!.operation).isEqualTo("op")
})
}, anyOrNull())
}

@Test
fun `when transaction is already active, does not start new transaction`() {
val scope = Scope(SentryOptions())
scope.setTransaction(io.sentry.SentryTracer(TransactionContext("aTransaction", "op"), hub))

whenever(hub.configureScope(any())).thenAnswer {
(it.arguments[0] as ScopeCallback).run(scope)
}
whenever(hub.span).thenReturn(SentryTracer(TransactionContext("aTransaction", "op"), hub))

sampleService.methodWithTransactionNameSet()

verify(hub, times(0)).captureTransaction(any(), any())
}

Expand All @@ -95,7 +94,7 @@ class SentryTransactionAdviceTest {
verify(hub).captureTransaction(check {
assertThat(it.transaction).isEqualTo("ClassAnnotatedSampleService.hello")
assertThat(it.contexts.trace!!.operation).isEqualTo("op")
})
}, anyOrNull())
}

@Test
Expand All @@ -104,7 +103,7 @@ class SentryTransactionAdviceTest {
verify(hub).captureTransaction(check {
assertThat(it.transaction).isEqualTo("ClassAnnotatedWithOperationSampleService.hello")
assertThat(it.contexts.trace!!.operation).isEqualTo("my-op")
})
}, anyOrNull())
}

@Test
Expand Down
Loading

0 comments on commit 8dcd504

Please sign in to comment.