Skip to content

Commit f553d7b

Browse files
authored
Add okhttp autoconfiguration for spring boot (#5797)
1 parent 872e8eb commit f553d7b

41 files changed

Lines changed: 1642 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Deprecate `sendDefaultPii` in favor of `dataCollection` ahead of its removal in 9.0 ([#6158](https://github.com/getsentry/sentry-java/pull/6158))
88
- Make the tombstone merge time threshold configurable via `SentryAndroidOptions.setTombstoneMergeTimeThresholdMillis` and the `io.sentry.tombstone.merge-time-threshold-millis` manifest option ([#6154](https://github.com/getsentry/sentry-java/pull/6154))
99
- Add `sentry-apollo-5` integration for Apollo Kotlin 5, providing HTTP tracing and failed GraphQL request reporting ([#6074](https://github.com/getsentry/sentry-java/pull/6074))
10+
- Add OkHttp autoconfiguration for Spring Boot ([#5797](https://github.com/getsentry/sentry-java/pull/5797))
1011

1112
### Fixes
1213

‎sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEvent.kt‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.sentry.IScopes
66
import io.sentry.ISpan
77
import io.sentry.SentryDate
88
import io.sentry.SpanDataConvention
9+
import io.sentry.SpanOptions
910
import io.sentry.TypeCheckHint
1011
import io.sentry.transport.CurrentDateProvider
1112
import io.sentry.util.Platform
@@ -40,8 +41,8 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
4041

4142
// We start the call span that will contain all the others
4243
val parentSpan = if (Platform.isAndroid()) scopes.transaction else scopes.span
43-
callSpan = parentSpan?.startChild("http.client")
44-
callSpan?.spanContext?.origin = TRACE_ORIGIN
44+
callSpan =
45+
parentSpan?.startChild("http.client", null, SpanOptions().apply { origin = TRACE_ORIGIN })
4546

4647
breadcrumb =
4748
Breadcrumb().apply {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.sentry.SentryIntegrationPackageStorage
1212
import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS
1313
import io.sentry.SentryReplayOptions
1414
import io.sentry.SpanDataConvention
15+
import io.sentry.SpanOptions
1516
import io.sentry.SpanStatus
1617
import io.sentry.TypeCheckHint.OKHTTP_REQUEST
1718
import io.sentry.TypeCheckHint.OKHTTP_RESPONSE
@@ -96,13 +97,16 @@ public open class SentryOkHttpInterceptor(
9697
// read the span from the bound scope
9798
okHttpEvent = null
9899
val parentSpan = if (Platform.isAndroid()) scopes.transaction else scopes.span
99-
span = parentSpan?.startChild("http.client", "$method $url")
100+
span =
101+
parentSpan?.startChild(
102+
"http.client",
103+
"$method $url",
104+
SpanOptions().apply { origin = TRACE_ORIGIN },
105+
)
100106
}
101107

102108
val startTimestamp = CurrentDateProvider.getInstance().currentTimeMillis
103109

104-
span?.spanContext?.origin = TRACE_ORIGIN
105-
106110
urlDetails.applyToSpan(span)
107111

108112
val isFromEventListener = okHttpEvent != null

‎sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventTest.kt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,21 @@ class SentryOkHttpEventTest {
100100
val callSpan = sut.callSpan
101101
assertNotNull(callSpan)
102102
assertEquals("http.client", callSpan.operation)
103+
assertEquals("auto.http.okhttp", callSpan.spanContext.origin)
103104
assertEquals("${fixture.mockRequest.method} ${fixture.mockRequest.url}", callSpan.description)
104105
assertEquals(fixture.mockRequest.url.toString(), callSpan.getData("url"))
105106
assertEquals(fixture.mockRequest.url.host, callSpan.getData("host"))
106107
assertEquals(fixture.mockRequest.url.encodedPath, callSpan.getData("path"))
107108
assertEquals(fixture.mockRequest.method, callSpan.getData(SpanDataConvention.HTTP_METHOD_KEY))
108109
}
109110

111+
@Test
112+
fun `when span origin is ignored, call span is a no-op`() {
113+
fixture.scopes.options.setIgnoredSpanOrigins(listOf("auto.http.okhttp"))
114+
val sut = fixture.getSut()
115+
assertTrue(sut.callSpan!!.isNoOp)
116+
}
117+
110118
@Test
111119
fun `when call span is null, breadcrumb is created anyway`() {
112120
val sut = fixture.getSut(currentSpan = null)

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ class SentryOkHttpInterceptorTest {
294294
assertTrue(httpClientSpan.isFinished)
295295
}
296296

297+
@Test
298+
fun `does not create a span when span origin is ignored`() {
299+
val sut = fixture.getSut { options ->
300+
options.setIgnoredSpanOrigins(listOf("auto.http.okhttp"))
301+
}
302+
sut.newCall(getRequest()).execute()
303+
assertEquals(0, fixture.sentryTracer.children.size)
304+
}
305+
297306
@Test
298307
fun `maps http status code to SpanStatus`() {
299308
val sut = fixture.getSut(httpStatusCode = 400)

‎sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ dependencies {
6464
implementation(libs.springboot4.starter.kafka)
6565
implementation(projects.sentryKafka)
6666

67+
// okhttp client instrumentation
68+
implementation(projects.sentryOkhttp)
69+
implementation(libs.okhttp)
70+
6771
// database query tracing
6872
implementation(projects.sentryJdbc)
6973
runtimeOnly(libs.hsqldb)

‎sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/SentryDemoApplication.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import io.sentry.samples.spring.boot4.quartz.SampleJob;
66
import java.util.Collections;
7+
import okhttp3.OkHttpClient;
78
import org.quartz.JobDetail;
89
import org.quartz.SimpleTrigger;
910
import org.springframework.boot.SpringApplication;
@@ -42,6 +43,12 @@ RestClient restClient(RestClient.Builder builder) {
4243
return builder.build();
4344
}
4445

46+
@Bean
47+
OkHttpClient okHttpClient() {
48+
// automatically instrumented by Sentry via sentry.clients.ok-http-enabled=true
49+
return new OkHttpClient.Builder().build();
50+
}
51+
4552
@Bean
4653
public JobDetailFactoryBean jobDetail() {
4754
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();

‎sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/TodoController.java‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package io.sentry.samples.spring.boot4;
22

33
import io.sentry.reactor.SentryReactorUtils;
4+
import java.io.IOException;
5+
import java.io.UncheckedIOException;
6+
import okhttp3.OkHttpClient;
7+
import okhttp3.Request;
8+
import okhttp3.Response;
9+
import okhttp3.ResponseBody;
410
import org.springframework.web.bind.annotation.GetMapping;
511
import org.springframework.web.bind.annotation.PathVariable;
612
import org.springframework.web.bind.annotation.RestController;
@@ -10,17 +16,27 @@
1016
import reactor.core.publisher.Hooks;
1117
import reactor.core.publisher.Mono;
1218
import reactor.core.scheduler.Schedulers;
19+
import tools.jackson.databind.ObjectMapper;
1320

1421
@RestController
1522
public class TodoController {
1623
private final RestTemplate restTemplate;
1724
private final WebClient webClient;
1825
private final RestClient restClient;
26+
private final OkHttpClient okHttpClient;
27+
private final ObjectMapper objectMapper;
1928

20-
public TodoController(RestTemplate restTemplate, WebClient webClient, RestClient restClient) {
29+
public TodoController(
30+
RestTemplate restTemplate,
31+
WebClient webClient,
32+
RestClient restClient,
33+
OkHttpClient okHttpClient,
34+
ObjectMapper objectMapper) {
2135
this.restTemplate = restTemplate;
2236
this.webClient = webClient;
2337
this.restClient = restClient;
38+
this.okHttpClient = okHttpClient;
39+
this.objectMapper = objectMapper;
2440
}
2541

2642
@GetMapping("/todo/{id}")
@@ -54,4 +70,19 @@ Todo todoRestClient(@PathVariable Long id) {
5470
.retrieve()
5571
.body(Todo.class);
5672
}
73+
74+
@GetMapping("/todo-okhttp/{id}")
75+
Todo todoOkHttp(@PathVariable Long id) {
76+
final Request request =
77+
new Request.Builder().url("https://jsonplaceholder.typicode.com/todos/" + id).build();
78+
try (Response response = okHttpClient.newCall(request).execute()) {
79+
final ResponseBody body = response.body();
80+
if (body == null) {
81+
throw new IllegalStateException("Response body was null");
82+
}
83+
return objectMapper.readValue(body.byteStream(), Todo.class);
84+
} catch (IOException e) {
85+
throw new UncheckedIOException(e);
86+
}
87+
}
5788
}

‎sentry-samples/sentry-samples-spring-boot-4/src/main/resources/application.properties‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ sentry.profile-session-sample-rate=1.0
2121
sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces
2222
sentry.profile-lifecycle=TRACE
2323
sentry.enable-cache-tracing=true
24+
# Automatically instrument Spring-managed OkHttpClient beans
25+
sentry.clients.ok-http-enabled=true
2426
spring.cache.cache-names=todos
2527
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
2628

‎sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,20 @@ class TodoSystemTest {
5858
)
5959
}
6060
}
61+
62+
@Test
63+
fun `get todo okhttp works`() {
64+
val restClient = testHelper.restClient
65+
restClient.getTodoOkHttp(1L)
66+
assertEquals(200, restClient.lastKnownStatusCode)
67+
68+
testHelper.ensureTransactionReceived { transaction, envelopeHeader ->
69+
transaction.transaction == "GET /todo-okhttp/{id}" &&
70+
testHelper.doesTransactionContainSpanWithOpAndDescription(
71+
transaction,
72+
"http.client",
73+
"GET https://jsonplaceholder.typicode.com/todos/1",
74+
)
75+
}
76+
}
6177
}

0 commit comments

Comments
 (0)