Skip to content

Commit a2fc547

Browse files
POTEL 63 - Let OpenTelemetry handle extracting and injecting tracing information (#3953)
* bump OTel to 2.10.0 * support DB_QUERY_TEXT * changelog * change bom version for otel * Replace OTel ContextStorage wrapper with ContextStorageProvider * bump spring boot 3.4 * fix twp * Use null sampled for TwP * make todo url configurable * Build PropagationContext from sampling decision in OTel instead of incoming headers * Format code * changelog * Revert "make todo url configurable" This reverts commit a90f316. * disable continueTrace and injecting tracing headers for ignored span origins * also change apollo and okhttp * changelog --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 339b7e0 commit a2fc547

File tree

26 files changed

+355
-40
lines changed

26 files changed

+355
-40
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- In `TransactionContext.fromPropagationContext` when there is no parent sampling decision, keep the decision `null` so a new sampling decision is made instead of defaulting to `false`
1111
- Defer sampling decision by setting `sampled` to `null` in `PropagationContext` when using OpenTelemetry in case of an incoming defer sampling `sentry-trace` header. ([#3945](https://github.com/getsentry/sentry-java/pull/3945))
1212
- Build `PropagationContext` from `SamplingDecision` made by `SentrySampler` instead of parsing headers and potentially ignoring a sampling decision in case a `sentry-trace` header comes in with deferred sampling decision. ([#3947](https://github.com/getsentry/sentry-java/pull/3947))
13+
- Let OpenTelemetry handle extracting and injecting tracing information ([#3953](https://github.com/getsentry/sentry-java/pull/3953))
14+
- Our integrations no longer call `.continueTrace` and also do not inject tracing headers if the integration has been added to `ignoredSpanOrigins`
1315

1416
## 8.0.0-rc.1
1517

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import io.sentry.util.HttpUtils
3131
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
3232
import io.sentry.util.Platform
3333
import io.sentry.util.PropagationTargetsUtils
34+
import io.sentry.util.SpanUtils
3435
import io.sentry.util.TracingUtils
3536
import io.sentry.util.UrlUtils
3637
import io.sentry.vendor.Base64
@@ -120,11 +121,13 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(
120121
private fun maybeAddTracingHeaders(scopes: IScopes, request: HttpRequest, span: ISpan?): HttpRequest {
121122
var cleanedHeaders = removeSentryInternalHeaders(request.headers).toMutableList()
122123

123-
TracingUtils.traceIfAllowed(scopes, request.url, request.headers.filter { it.name == BaggageHeader.BAGGAGE_HEADER }.map { it.value }, span)?.let {
124-
cleanedHeaders.add(HttpHeader(it.sentryTraceHeader.name, it.sentryTraceHeader.value))
125-
it.baggageHeader?.let { baggageHeader ->
126-
cleanedHeaders = cleanedHeaders.filterNot { it.name == BaggageHeader.BAGGAGE_HEADER }.toMutableList().apply {
127-
add(HttpHeader(baggageHeader.name, baggageHeader.value))
124+
if (!isIgnored()) {
125+
TracingUtils.traceIfAllowed(scopes, request.url, request.headers.filter { it.name == BaggageHeader.BAGGAGE_HEADER }.map { it.value }, span)?.let {
126+
cleanedHeaders.add(HttpHeader(it.sentryTraceHeader.name, it.sentryTraceHeader.value))
127+
it.baggageHeader?.let { baggageHeader ->
128+
cleanedHeaders = cleanedHeaders.filterNot { it.name == BaggageHeader.BAGGAGE_HEADER }.toMutableList().apply {
129+
add(HttpHeader(baggageHeader.name, baggageHeader.value))
130+
}
128131
}
129132
}
130133
}
@@ -136,6 +139,10 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(
136139
return requestBuilder.build()
137140
}
138141

142+
private fun isIgnored(): Boolean {
143+
return SpanUtils.isIgnored(scopes.getOptions().getIgnoredSpanOrigins(), TRACE_ORIGIN)
144+
}
145+
139146
private fun removeSentryInternalHeaders(headers: List<HttpHeader>): List<HttpHeader> {
140147
return headers.filterNot {
141148
it.name.equals(SENTRY_APOLLO_3_VARIABLES, true) ||

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,16 @@ class SentryApollo3InterceptorTest {
208208
assertNotNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
209209
}
210210

211+
@Test
212+
fun `does not add sentry-trace header when span origin is ignored`() {
213+
fixture.options.ignoredSpanOrigins = listOf("auto.graphql.apollo3")
214+
executeQuery(isSpanActive = false)
215+
216+
val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
217+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
218+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
219+
}
220+
211221
@Test
212222
fun `when there is an active span, adds sentry trace headers to the request`() {
213223
executeQuery()

sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.sentry.SpanStatus
2525
import io.sentry.TypeCheckHint.APOLLO_REQUEST
2626
import io.sentry.TypeCheckHint.APOLLO_RESPONSE
2727
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
28+
import io.sentry.util.SpanUtils
2829
import io.sentry.util.TracingUtils
2930
import java.util.Locale
3031
import java.util.concurrent.Executor
@@ -115,7 +116,7 @@ class SentryApolloInterceptor(
115116
private fun addTracingHeaders(request: InterceptorRequest, span: ISpan?): RequestHeaders {
116117
val requestHeaderBuilder = request.requestHeaders.toBuilder()
117118

118-
if (scopes.options.isTraceSampling) {
119+
if (scopes.options.isTraceSampling && !isIgnored()) {
119120
// we have no access to URI, no way to verify tracing origins
120121
TracingUtils.trace(
121122
scopes,
@@ -135,6 +136,10 @@ class SentryApolloInterceptor(
135136
return requestHeaderBuilder.build()
136137
}
137138

139+
private fun isIgnored(): Boolean {
140+
return SpanUtils.isIgnored(scopes.getOptions().getIgnoredSpanOrigins(), TRACE_ORIGIN)
141+
}
142+
138143
private fun startChild(request: InterceptorRequest, activeSpan: ISpan): ISpan {
139144
val operation = request.operation.name().name()
140145
val operationType = when (request.operation) {

sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ class SentryApolloInterceptorTest {
161161
assertNotNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
162162
}
163163

164+
@Test
165+
fun `does not add sentry-trace header when span origin is ignored`() {
166+
fixture.options.ignoredSpanOrigins = listOf("auto.graphql.apollo")
167+
executeQuery(isSpanActive = false)
168+
169+
val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
170+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
171+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
172+
}
173+
164174
@Test
165175
fun `when there is an active span, adds sentry trace headers to the request`() {
166176
executeQuery()

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import io.sentry.transport.CurrentDateProvider
1818
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
1919
import io.sentry.util.Platform
2020
import io.sentry.util.PropagationTargetsUtils
21+
import io.sentry.util.SpanUtils
2122
import io.sentry.util.TracingUtils
2223
import io.sentry.util.UrlUtils
2324
import okhttp3.Interceptor
@@ -93,16 +94,21 @@ public open class SentryOkHttpInterceptor(
9394
try {
9495
val requestBuilder = request.newBuilder()
9596

96-
TracingUtils.traceIfAllowed(
97-
scopes,
98-
request.url.toString(),
99-
request.headers(BaggageHeader.BAGGAGE_HEADER),
100-
span
101-
)?.let { tracingHeaders ->
102-
requestBuilder.addHeader(tracingHeaders.sentryTraceHeader.name, tracingHeaders.sentryTraceHeader.value)
103-
tracingHeaders.baggageHeader?.let {
104-
requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER)
105-
requestBuilder.addHeader(it.name, it.value)
97+
if (!isIgnored()) {
98+
TracingUtils.traceIfAllowed(
99+
scopes,
100+
request.url.toString(),
101+
request.headers(BaggageHeader.BAGGAGE_HEADER),
102+
span
103+
)?.let { tracingHeaders ->
104+
requestBuilder.addHeader(
105+
tracingHeaders.sentryTraceHeader.name,
106+
tracingHeaders.sentryTraceHeader.value
107+
)
108+
tracingHeaders.baggageHeader?.let {
109+
requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER)
110+
requestBuilder.addHeader(it.name, it.value)
111+
}
106112
}
107113
}
108114

@@ -144,6 +150,10 @@ public open class SentryOkHttpInterceptor(
144150
}
145151
}
146152

153+
private fun isIgnored(): Boolean {
154+
return SpanUtils.isIgnored(scopes.getOptions().getIgnoredSpanOrigins(), TRACE_ORIGIN)
155+
}
156+
147157
private fun sendBreadcrumb(
148158
request: Request,
149159
code: Int?,

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.sentry.IScope
1010
import io.sentry.IScopes
1111
import io.sentry.Scope
1212
import io.sentry.ScopeCallback
13+
import io.sentry.Sentry
1314
import io.sentry.SentryOptions
1415
import io.sentry.SentryTraceHeader
1516
import io.sentry.SentryTracer
@@ -73,16 +74,18 @@ class SentryOkHttpInterceptorTest {
7374
HttpStatusCodeRange.DEFAULT_MAX
7475
)
7576
),
76-
sendDefaultPii: Boolean = false
77+
sendDefaultPii: Boolean = false,
78+
optionsConfiguration: Sentry.OptionsConfiguration<SentryOptions>? = null
7779
): OkHttpClient {
78-
options = SentryOptions().apply {
79-
dsn = "https://[email protected]/proj"
80+
options = SentryOptions().also {
81+
optionsConfiguration?.configure(it)
82+
it.dsn = "https://[email protected]/proj"
8083
if (includeMockServerInTracePropagationTargets) {
81-
setTracePropagationTargets(listOf(server.hostName))
84+
it.setTracePropagationTargets(listOf(server.hostName))
8285
} else if (!keepDefaultTracePropagationTargets) {
83-
setTracePropagationTargets(listOf("other-api"))
86+
it.setTracePropagationTargets(listOf("other-api"))
8487
}
85-
isSendDefaultPii = sendDefaultPii
88+
it.isSendDefaultPii = sendDefaultPii
8689
}
8790
scope = Scope(options)
8891
whenever(scopes.options).thenReturn(options)
@@ -207,6 +210,17 @@ class SentryOkHttpInterceptorTest {
207210
assertNotNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
208211
}
209212

213+
@Test
214+
fun `does not add sentry-trace header when span origin is ignored`() {
215+
val sut = fixture.getSut(isSpanActive = false) { options ->
216+
options.ignoredSpanOrigins = listOf("auto.http.okhttp")
217+
}
218+
sut.newCall(getRequest()).execute()
219+
val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
220+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
221+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
222+
}
223+
210224
@Test
211225
fun `when there is no active span and host if not allowed, does not add sentry trace header to the request`() {
212226
val sut = fixture.getSut(isSpanActive = false)

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.SpanOptions;
1616
import io.sentry.SpanStatus;
1717
import io.sentry.util.Objects;
18+
import io.sentry.util.SpanUtils;
1819
import io.sentry.util.TracingUtils;
1920
import io.sentry.util.UrlUtils;
2021
import java.io.IOException;
@@ -98,6 +99,10 @@ public Response execute(final @NotNull Request request, final @NotNull Request.O
9899

99100
private @NotNull Request maybeAddTracingHeaders(
100101
final @NotNull Request request, final @Nullable ISpan span) {
102+
if (isIgnored()) {
103+
return request;
104+
}
105+
101106
final @NotNull RequestWrapper requestWrapper = new RequestWrapper(request);
102107
final @Nullable Collection<String> requestBaggageHeaders =
103108
request.headers().get(BaggageHeader.BAGGAGE_HEADER);
@@ -124,6 +129,10 @@ public Response execute(final @NotNull Request request, final @NotNull Request.O
124129
return requestWrapper.build();
125130
}
126131

132+
private boolean isIgnored() {
133+
return SpanUtils.isIgnored(scopes.getOptions().getIgnoredSpanOrigins(), TRACE_ORIGIN);
134+
}
135+
127136
private void addBreadcrumb(final @NotNull Request request, final @Nullable Response response) {
128137
final Breadcrumb breadcrumb =
129138
Breadcrumb.http(

sentry-openfeign/src/test/kotlin/io/sentry/openfeign/SentryFeignClientTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ class SentryFeignClientTest {
130130
assertNotNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
131131
}
132132

133+
@Test
134+
fun `does not add sentry trace header when span origin is ignored`() {
135+
fixture.sentryOptions.dsn = "https://[email protected]/proj"
136+
fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.openfeign")
137+
val sut = fixture.getSut(isSpanActive = false)
138+
sut.getOk()
139+
val recorderRequest = fixture.server.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
140+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
141+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
142+
}
143+
133144
@Test
134145
fun `when there is no active span, does not add sentry trace header to the request if host is disallowed`() {
135146
fixture.sentryOptions.setTracePropagationTargets(listOf("some-host-that-does-not-exist"))

sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestClientCustomizerTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import okhttp3.mockwebserver.MockWebServer
1717
import okhttp3.mockwebserver.SocketPolicy
1818
import org.apache.hc.client5.http.impl.classic.HttpClients
1919
import org.assertj.core.api.Assertions.assertThat
20+
import org.junit.Assert.assertNull
2021
import org.mockito.Mockito.doAnswer
2122
import org.mockito.Mockito.mock
2223
import org.mockito.Mockito.verify
@@ -248,6 +249,24 @@ class SentrySpanRestClientCustomizerTest {
248249
assertTrue(baggageHeaderValues[0].contains("sentry-trace_id"))
249250
}
250251

252+
@Test
253+
fun `does not add sentry-trace header if span origin is ignored`() {
254+
fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.restclient")
255+
val sut = fixture.getSut(isTransactionActive = false)
256+
val headers = HttpHeaders()
257+
258+
sut.build()
259+
.get()
260+
.uri(fixture.url)
261+
.httpRequest { it.headers.addAll(headers) }
262+
.retrieve()
263+
.toEntity(String::class.java)
264+
265+
val recorderRequest = fixture.mockServer.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
266+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
267+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
268+
}
269+
251270
@Test
252271
fun `when transaction is active adds breadcrumb when http calls succeeds`() {
253272
fixture.getSut(isTransactionActive = true)

sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import okhttp3.mockwebserver.MockResponse
1616
import okhttp3.mockwebserver.MockWebServer
1717
import okhttp3.mockwebserver.SocketPolicy
1818
import org.assertj.core.api.Assertions.assertThat
19+
import org.junit.Assert.assertNull
1920
import org.mockito.kotlin.any
2021
import org.mockito.kotlin.anyOrNull
2122
import org.mockito.kotlin.check
@@ -198,6 +199,20 @@ class SentrySpanRestTemplateCustomizerTest {
198199
assertTrue(baggageHeaderValues[0].contains("sentry-trace_id"))
199200
}
200201

202+
@Test
203+
fun `does not add sentry-trace header when span origin is ignored`() {
204+
fixture.sentryOptions.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.resttemplate")
205+
val sut = fixture.getSut(isTransactionActive = false)
206+
val headers = HttpHeaders()
207+
val requestEntity = HttpEntity<Unit>(headers)
208+
209+
sut.exchange(fixture.url, HttpMethod.GET, requestEntity, String::class.java)
210+
211+
val recorderRequest = fixture.mockServer.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
212+
assertNull(recorderRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
213+
assertNull(recorderRequest.headers[BaggageHeader.BAGGAGE_HEADER])
214+
}
215+
201216
@Test
202217
fun `avoids duplicate registration`() {
203218
val restTemplate = fixture.getSut(isTransactionActive = true)

sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanWebClientCustomizerTest.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.sentry.IScope
66
import io.sentry.IScopes
77
import io.sentry.Scope
88
import io.sentry.ScopeCallback
9+
import io.sentry.Sentry.OptionsConfiguration
910
import io.sentry.SentryOptions
1011
import io.sentry.SentryTraceHeader
1112
import io.sentry.SentryTracer
@@ -46,14 +47,15 @@ class SentrySpanWebClientCustomizerTest {
4647
lateinit var transaction: SentryTracer
4748
private val customizer = SentrySpanWebClientCustomizer(scopes)
4849

49-
fun getSut(isTransactionActive: Boolean, status: HttpStatus = HttpStatus.OK, throwIOException: Boolean = false, includeMockServerInTracingOrigins: Boolean = true): WebClient {
50-
sentryOptions = SentryOptions().apply {
50+
fun getSut(isTransactionActive: Boolean, status: HttpStatus = HttpStatus.OK, throwIOException: Boolean = false, includeMockServerInTracingOrigins: Boolean = true, optionsConfiguration: OptionsConfiguration<SentryOptions>? = null): WebClient {
51+
sentryOptions = SentryOptions().also {
52+
optionsConfiguration?.configure(it)
5153
if (includeMockServerInTracingOrigins) {
52-
setTracePropagationTargets(listOf(mockServer.hostName))
54+
it.setTracePropagationTargets(listOf(mockServer.hostName))
5355
} else {
54-
setTracePropagationTargets(listOf("other-api"))
56+
it.setTracePropagationTargets(listOf("other-api"))
5557
}
56-
dsn = "http://key@localhost/proj"
58+
it.dsn = "http://key@localhost/proj"
5759
}
5860
scope = Scope(sentryOptions)
5961
whenever(scopes.options).thenReturn(sentryOptions)
@@ -163,6 +165,22 @@ class SentrySpanWebClientCustomizerTest {
163165
assertNotNull(recordedRequest.headers[BaggageHeader.BAGGAGE_HEADER])
164166
}
165167

168+
@Test
169+
fun `does not add sentry-trace header when span origin is ignored`() {
170+
val sut = fixture.getSut(isTransactionActive = false, includeMockServerInTracingOrigins = true) { options ->
171+
options.ignoredSpanOrigins = listOf("auto.http.spring_jakarta.webclient")
172+
}
173+
sut
174+
.get()
175+
.uri(fixture.mockServer.url("/test/123").toUri())
176+
.retrieve()
177+
.bodyToMono(String::class.java)
178+
.block()
179+
val recordedRequest = fixture.mockServer.takeRequest(mockServerRequestTimeoutMillis, TimeUnit.MILLISECONDS)!!
180+
assertNull(recordedRequest.headers[SentryTraceHeader.SENTRY_TRACE_HEADER])
181+
assertNull(recordedRequest.headers[BaggageHeader.BAGGAGE_HEADER])
182+
}
183+
166184
@Test
167185
fun `when transaction is active and server is listed in tracing origins, adds sentry trace header to the request`() {
168186
fixture.getSut(isTransactionActive = true)

0 commit comments

Comments
 (0)