Skip to content
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## Unreleased

### Features

- Add support for w3c traceparent header ([#4671](https://github.com/getsentry/sentry-java/pull/4671))
- This feature is disabled by default. If enabled, outgoing requests will include the w3c `traceparent` header.
- See https://develop.sentry.dev/sdk/telemetry/traces/distributed-tracing/#w3c-trace-context-header for more details.
```kotlin
Sentry(Android).init(context) { options ->
// ...
options.isPropagateTraceparent = true
}
```

## 8.21.1

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ constructor(
.toMutableList()
.apply { add(HttpHeader(baggageHeader.name, baggageHeader.value)) }
}
it.w3cTraceparentHeader?.let { w3cHeader ->
cleanedHeaders.add(HttpHeader(w3cHeader.name, w3cHeader.value))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.sentry.SpanStatus
import io.sentry.TraceContext
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import io.sentry.W3CTraceparentHeader
import io.sentry.apollo3.SentryApollo3HttpInterceptor.BeforeSpanCallback
import io.sentry.mockServerRequestTimeoutMillis
import io.sentry.protocol.SdkVersion
Expand Down Expand Up @@ -351,6 +352,26 @@ class SentryApollo3InterceptorTest {
verify(fixture.scopes).span
}

@Test
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
fixture.options.isPropagateTraceparent = true
executeQuery()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNotNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

@Test
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
fixture.options.isPropagateTraceparent = false
executeQuery()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

private fun assertTransactionDetails(
it: SentryTransaction,
httpStatusCode: Int? = 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ constructor(
.toMutableList()
.apply { add(HttpHeader(baggageHeader.name, baggageHeader.value)) }
}
it.w3cTraceparentHeader?.let { w3cHeader ->
cleanedHeaders.add(HttpHeader(w3cHeader.name, w3cHeader.value))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import io.sentry.SpanStatus
import io.sentry.TraceContext
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import io.sentry.W3CTraceparentHeader
import io.sentry.apollo4.SentryApollo4HttpInterceptor.BeforeSpanCallback
import io.sentry.apollo4.generated.LaunchDetailsQuery
import io.sentry.mockServerRequestTimeoutMillis
Expand Down Expand Up @@ -363,6 +364,26 @@ abstract class SentryApollo4HttpInterceptorTest(
verify(fixture.scopes).span
}

@Test
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
fixture.options.isPropagateTraceparent = true
executeQuery()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNotNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

@Test
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
fixture.options.isPropagateTraceparent = false
executeQuery()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

private fun assertTransactionDetails(
it: SentryTransaction,
httpStatusCode: Int? = 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public val SentryKtorClientPlugin: ClientPlugin<SentryKtorClientPluginConfig> =
request.headers.remove(BaggageHeader.BAGGAGE_HEADER)
request.headers[it.name] = it.value
}
tracingHeaders.w3cTraceparentHeader?.let { request.headers[it.name] = it.value }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.sentry.SentryTracer
import io.sentry.SpanDataConvention
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.W3CTraceparentHeader
import io.sentry.exception.SentryHttpClientException
import io.sentry.mockServerRequestTimeoutMillis
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -426,4 +427,29 @@ class SentryKtorClientPluginTest {
assertTrue(baggageHeaderValues[0].contains("sentry-transaction=name"))
assertTrue(baggageHeaderValues[0].contains("sentry-trace_id"))
}

@Test
fun `adds W3C traceparent header when propagateTraceparent is enabled`(): Unit = runBlocking {
val sut =
fixture.getSut(optionsConfiguration = { options -> options.isPropagateTraceparent = true })
sut.get(fixture.server.url("/hello").toString())

val recordedRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recordedRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNotNull(recordedRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

@Test
fun `does not add W3C traceparent header when propagateTraceparent is disabled`(): Unit =
runBlocking {
val sut =
fixture.getSut(optionsConfiguration = { options -> options.isPropagateTraceparent = false })
sut.get(fixture.server.url("/hello").toString())

val recordedRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recordedRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNull(recordedRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public open class SentryOkHttpInterceptor(
requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER)
requestBuilder.addHeader(it.name, it.value)
}
tracingHeaders.w3cTraceparentHeader?.let { requestBuilder.addHeader(it.name, it.value) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.sentry.SpanDataConvention
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.TypeCheckHint
import io.sentry.W3CTraceparentHeader
import io.sentry.exception.SentryHttpClientException
import io.sentry.mockServerRequestTimeoutMillis
import java.io.IOException
Expand Down Expand Up @@ -645,4 +646,38 @@ class SentryOkHttpInterceptorTest {
val okHttpEvent = SentryOkHttpEventListener.eventMap[call]!!
assertTrue(okHttpEvent.isEventFinished.get())
}

@Test
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
val client =
fixture.getSut(
optionsConfiguration = Sentry.OptionsConfiguration { it.isPropagateTraceparent = true }
)

fixture.server.enqueue(MockResponse().setResponseCode(200))

val request = getRequest("/test")
client.newCall(request).execute()

val recordedRequest = fixture.server.takeRequest()
assertNotNull(recordedRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER))
assertNotNull(recordedRequest.getHeader(W3CTraceparentHeader.TRACEPARENT_HEADER))
}

@Test
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
val client =
fixture.getSut(
optionsConfiguration = Sentry.OptionsConfiguration { it.isPropagateTraceparent = false }
)

fixture.server.enqueue(MockResponse().setResponseCode(200))

val request = getRequest("/test")
client.newCall(request).execute()

val recordedRequest = fixture.server.takeRequest()
assertNotNull(recordedRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER))
assertNull(recordedRequest.getHeader(W3CTraceparentHeader.TRACEPARENT_HEADER))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.sentry.SpanDataConvention;
import io.sentry.SpanOptions;
import io.sentry.SpanStatus;
import io.sentry.W3CTraceparentHeader;
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import io.sentry.util.TracingUtils;
Expand Down Expand Up @@ -137,6 +138,12 @@ public Response execute(final @NotNull Request request, final @NotNull Request.O
requestWrapper.removeHeader(BaggageHeader.BAGGAGE_HEADER);
requestWrapper.header(baggageHeader.getName(), baggageHeader.getValue());
}

final @Nullable W3CTraceparentHeader w3cTraceparentHeader =
tracingHeaders.getW3cTraceparentHeader();
if (w3cTraceparentHeader != null) {
requestWrapper.header(w3cTraceparentHeader.getName(), w3cTraceparentHeader.getValue());
}
}

return requestWrapper.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.sentry.SentryTracer
import io.sentry.SpanDataConvention
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.W3CTraceparentHeader
import io.sentry.mockServerRequestTimeoutMillis
import java.util.concurrent.TimeUnit
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -316,6 +317,32 @@ class SentryFeignClientTest {
assertNotNull(httpClientSpan.spanContext.sampled) { assertFalse(it) }
}

@Test
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
fixture.sentryOptions.isTraceSampling = true
fixture.sentryOptions.isPropagateTraceparent = true
fixture.sentryOptions.dsn = "https://[email protected]/proj"
val sut = fixture.getSut()
sut.getOk()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNotNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

@Test
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
fixture.sentryOptions.isTraceSampling = true
fixture.sentryOptions.isPropagateTraceparent = false
fixture.sentryOptions.dsn = "https://[email protected]/proj"
val sut = fixture.getSut()
sut.getOk()
val recorderRequest =
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
assertNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
}

interface MockApi {
@RequestLine("GET /status/200") fun getOk(): String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.sentry.SpanDataConvention;
import io.sentry.SpanOptions;
import io.sentry.SpanStatus;
import io.sentry.W3CTraceparentHeader;
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import io.sentry.util.TracingUtils;
Expand Down Expand Up @@ -113,6 +114,12 @@ private void maybeAddTracingHeaders(
if (baggageHeader != null) {
request.getHeaders().set(baggageHeader.getName(), baggageHeader.getValue());
}

final @Nullable W3CTraceparentHeader w3cTraceparentHeader =
tracingHeaders.getW3cTraceparentHeader();
if (w3cTraceparentHeader != null) {
request.getHeaders().add(w3cTraceparentHeader.getName(), w3cTraceparentHeader.getValue());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.sentry.spring7.tracing

import io.sentry.IScopes
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.Sentry
import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
import io.sentry.W3CTraceparentHeader
import java.net.URI
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.springframework.http.HttpMethod
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpResponse
import org.springframework.mock.http.client.MockClientHttpRequest
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
class SentrySpanClientHttpRequestInterceptorTest {

class Fixture {
val request = MockClientHttpRequest(HttpMethod.GET, URI.create("https://example.com/users/123"))

val options =
SentryOptions().apply {
dsn = "https://[email protected]/proj"
tracesSampleRate = 1.0
}
val scope = Scope(options)

val scopes = mock<IScopes>()
val requestExecution = mock<ClientHttpRequestExecution>()
val body = "data".toByteArray()

init {
whenever(scopes.options).thenReturn(options)
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())

whenever(requestExecution.execute(any(), any())).thenReturn(mock<ClientHttpResponse>())
}

fun create(
config: Sentry.OptionsConfiguration<SentryOptions>
): SentrySpanClientHttpRequestInterceptor {
config.configure(options)
return SentrySpanClientHttpRequestInterceptor(scopes)
}
}

val fixture = Fixture()

@Test
fun `attaches w3c trace parent header when enabled`() {
val sut = fixture.create { options -> options.isPropagateTraceparent = true }
sut.intercept(fixture.request, fixture.body, fixture.requestExecution)

assertNotNull(fixture.request.headers.get(SentryTraceHeader.SENTRY_TRACE_HEADER))
assertNotNull(fixture.request.headers.get(W3CTraceparentHeader.TRACEPARENT_HEADER))
}

@Test
fun `does not attach w3c trace parent header when disabled`() {
val sut = fixture.create { options -> options.isPropagateTraceparent = false }
sut.intercept(fixture.request, fixture.body, fixture.requestExecution)

assertNotNull(fixture.request.headers.get(SentryTraceHeader.SENTRY_TRACE_HEADER))
assertNull(fixture.request.headers.get(W3CTraceparentHeader.TRACEPARENT_HEADER))
}
}
Loading
Loading