Skip to content

Commit 278b42e

Browse files
Add support for w3c traceparent header (#4671)
* Add support for w3c traceparent header * Update Changelog * Add traceheader to integrattions, address feedback * Format code * Refine Changelog * Cleanup AI slop * Add missing version to header value * Add more tests * Add more tests * Fix test naming * Update CHANGELOG.md --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 119eeca commit 278b42e

File tree

29 files changed

+795
-5
lines changed

29 files changed

+795
-5
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add support for w3c traceparent header ([#4671](https://github.com/getsentry/sentry-java/pull/4671))
8+
- This feature is disabled by default. If enabled, outgoing requests will include the w3c `traceparent` header.
9+
- See https://develop.sentry.dev/sdk/telemetry/traces/distributed-tracing/#w3c-trace-context-header for more details.
10+
```kotlin
11+
Sentry(Android).init(context) { options ->
12+
// ...
13+
options.isPropagateTraceparent = true
14+
}
15+
```
16+
317
## 8.21.1
418

519
### Fixes

sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ constructor(
132132
.toMutableList()
133133
.apply { add(HttpHeader(baggageHeader.name, baggageHeader.value)) }
134134
}
135+
it.w3cTraceparentHeader?.let { w3cHeader ->
136+
cleanedHeaders.add(HttpHeader(w3cHeader.name, w3cHeader.value))
137+
}
135138
}
136139
}
137140

sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.sentry.SpanStatus
2323
import io.sentry.TraceContext
2424
import io.sentry.TracesSamplingDecision
2525
import io.sentry.TransactionContext
26+
import io.sentry.W3CTraceparentHeader
2627
import io.sentry.apollo3.SentryApollo3HttpInterceptor.BeforeSpanCallback
2728
import io.sentry.mockServerRequestTimeoutMillis
2829
import io.sentry.protocol.SdkVersion
@@ -351,6 +352,26 @@ class SentryApollo3InterceptorTest {
351352
verify(fixture.scopes).span
352353
}
353354

355+
@Test
356+
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
357+
fixture.options.isPropagateTraceparent = true
358+
executeQuery()
359+
val recorderRequest =
360+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
361+
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
362+
assertNotNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
363+
}
364+
365+
@Test
366+
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
367+
fixture.options.isPropagateTraceparent = false
368+
executeQuery()
369+
val recorderRequest =
370+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
371+
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
372+
assertNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
373+
}
374+
354375
private fun assertTransactionDetails(
355376
it: SentryTransaction,
356377
httpStatusCode: Int? = 200,

sentry-apollo-4/src/main/java/io/sentry/apollo4/SentryApollo4HttpInterceptor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ constructor(
132132
.toMutableList()
133133
.apply { add(HttpHeader(baggageHeader.name, baggageHeader.value)) }
134134
}
135+
it.w3cTraceparentHeader?.let { w3cHeader ->
136+
cleanedHeaders.add(HttpHeader(w3cHeader.name, w3cHeader.value))
137+
}
135138
}
136139
}
137140

sentry-apollo-4/src/test/java/io/sentry/apollo4/SentryApollo4HttpInterceptorTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.sentry.SpanStatus
2626
import io.sentry.TraceContext
2727
import io.sentry.TracesSamplingDecision
2828
import io.sentry.TransactionContext
29+
import io.sentry.W3CTraceparentHeader
2930
import io.sentry.apollo4.SentryApollo4HttpInterceptor.BeforeSpanCallback
3031
import io.sentry.apollo4.generated.LaunchDetailsQuery
3132
import io.sentry.mockServerRequestTimeoutMillis
@@ -363,6 +364,26 @@ abstract class SentryApollo4HttpInterceptorTest(
363364
verify(fixture.scopes).span
364365
}
365366

367+
@Test
368+
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
369+
fixture.options.isPropagateTraceparent = true
370+
executeQuery()
371+
val recorderRequest =
372+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
373+
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
374+
assertNotNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
375+
}
376+
377+
@Test
378+
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
379+
fixture.options.isPropagateTraceparent = false
380+
executeQuery()
381+
val recorderRequest =
382+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
383+
assertNotNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
384+
assertNull(recorderRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
385+
}
386+
366387
private fun assertTransactionDetails(
367388
it: SentryTransaction,
368389
httpStatusCode: Int? = 200,

sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public val SentryKtorClientPlugin: ClientPlugin<SentryKtorClientPluginConfig> =
137137
request.headers.remove(BaggageHeader.BAGGAGE_HEADER)
138138
request.headers[it.name] = it.value
139139
}
140+
tracingHeaders.w3cTraceparentHeader?.let { request.headers[it.name] = it.value }
140141
}
141142
}
142143
}

sentry-ktor-client/src/test/java/io/sentry/ktorClient/SentryKtorClientPluginTest.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.sentry.SentryTracer
2424
import io.sentry.SpanDataConvention
2525
import io.sentry.SpanStatus
2626
import io.sentry.TransactionContext
27+
import io.sentry.W3CTraceparentHeader
2728
import io.sentry.exception.SentryHttpClientException
2829
import io.sentry.mockServerRequestTimeoutMillis
2930
import java.util.concurrent.TimeUnit
@@ -426,4 +427,29 @@ class SentryKtorClientPluginTest {
426427
assertTrue(baggageHeaderValues[0].contains("sentry-transaction=name"))
427428
assertTrue(baggageHeaderValues[0].contains("sentry-trace_id"))
428429
}
430+
431+
@Test
432+
fun `adds W3C traceparent header when propagateTraceparent is enabled`(): Unit = runBlocking {
433+
val sut =
434+
fixture.getSut(optionsConfiguration = { options -> options.isPropagateTraceparent = true })
435+
sut.get(fixture.server.url("/hello").toString())
436+
437+
val recordedRequest =
438+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
439+
assertNotNull(recordedRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
440+
assertNotNull(recordedRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
441+
}
442+
443+
@Test
444+
fun `does not add W3C traceparent header when propagateTraceparent is disabled`(): Unit =
445+
runBlocking {
446+
val sut =
447+
fixture.getSut(optionsConfiguration = { options -> options.isPropagateTraceparent = false })
448+
sut.get(fixture.server.url("/hello").toString())
449+
450+
val recordedRequest =
451+
fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
452+
assertNotNull(recordedRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
453+
assertNull(recordedRequest.headers[W3CTraceparentHeader.TRACEPARENT_HEADER])
454+
}
429455
}

sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpInterceptor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public open class SentryOkHttpInterceptor(
116116
requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER)
117117
requestBuilder.addHeader(it.name, it.value)
118118
}
119+
tracingHeaders.w3cTraceparentHeader?.let { requestBuilder.addHeader(it.name, it.value) }
119120
}
120121
}
121122

sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpInterceptorTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import io.sentry.SpanDataConvention
1919
import io.sentry.SpanStatus
2020
import io.sentry.TransactionContext
2121
import io.sentry.TypeCheckHint
22+
import io.sentry.W3CTraceparentHeader
2223
import io.sentry.exception.SentryHttpClientException
2324
import io.sentry.mockServerRequestTimeoutMillis
2425
import java.io.IOException
@@ -645,4 +646,38 @@ class SentryOkHttpInterceptorTest {
645646
val okHttpEvent = SentryOkHttpEventListener.eventMap[call]!!
646647
assertTrue(okHttpEvent.isEventFinished.get())
647648
}
649+
650+
@Test
651+
fun `adds W3C traceparent header when propagateTraceparent is enabled`() {
652+
val client =
653+
fixture.getSut(
654+
optionsConfiguration = Sentry.OptionsConfiguration { it.isPropagateTraceparent = true }
655+
)
656+
657+
fixture.server.enqueue(MockResponse().setResponseCode(200))
658+
659+
val request = getRequest("/test")
660+
client.newCall(request).execute()
661+
662+
val recordedRequest = fixture.server.takeRequest()
663+
assertNotNull(recordedRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER))
664+
assertNotNull(recordedRequest.getHeader(W3CTraceparentHeader.TRACEPARENT_HEADER))
665+
}
666+
667+
@Test
668+
fun `does not add W3C traceparent header when propagateTraceparent is disabled`() {
669+
val client =
670+
fixture.getSut(
671+
optionsConfiguration = Sentry.OptionsConfiguration { it.isPropagateTraceparent = false }
672+
)
673+
674+
fixture.server.enqueue(MockResponse().setResponseCode(200))
675+
676+
val request = getRequest("/test")
677+
client.newCall(request).execute()
678+
679+
val recordedRequest = fixture.server.takeRequest()
680+
assertNotNull(recordedRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER))
681+
assertNull(recordedRequest.getHeader(W3CTraceparentHeader.TRACEPARENT_HEADER))
682+
}
648683
}

sentry-openfeign/src/main/java/io/sentry/openfeign/SentryFeignClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.sentry.SpanDataConvention;
1818
import io.sentry.SpanOptions;
1919
import io.sentry.SpanStatus;
20+
import io.sentry.W3CTraceparentHeader;
2021
import io.sentry.util.Objects;
2122
import io.sentry.util.SpanUtils;
2223
import io.sentry.util.TracingUtils;
@@ -137,6 +138,12 @@ public Response execute(final @NotNull Request request, final @NotNull Request.O
137138
requestWrapper.removeHeader(BaggageHeader.BAGGAGE_HEADER);
138139
requestWrapper.header(baggageHeader.getName(), baggageHeader.getValue());
139140
}
141+
142+
final @Nullable W3CTraceparentHeader w3cTraceparentHeader =
143+
tracingHeaders.getW3cTraceparentHeader();
144+
if (w3cTraceparentHeader != null) {
145+
requestWrapper.header(w3cTraceparentHeader.getName(), w3cTraceparentHeader.getValue());
146+
}
140147
}
141148

142149
return requestWrapper.build();

0 commit comments

Comments
 (0)