Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Add OkHttp autoconfiguration for Spring Boot ([#5797](https://github.com/getsentry/sentry-java/pull/5797))
- Add `LocalSentrySpan` to `sentry-compose` so apps can provide a parent `ISpan` to a composable subtree and have nested `SentryTraced` spans attach to it ([#6112]https://github.com/getsentry/sentry-java/pull/6112)
- Add `dataCollection`, a fine-grained replacement for `sendDefaultPii`, for controlling data collected automatically by SDK integrations ([#5759](https://github.com/getsentry/sentry-java/pull/5759))
- `sendDefaultPii` remains supported for backwards compatibility. When `dataCollection` is not configured, the SDK preserves the existing `sendDefaultPii` behavior.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ dependencies {
implementation(libs.springboot4.starter.kafka)
implementation(projects.sentryKafka)

// okhttp client instrumentation
implementation(projects.sentryOkhttp)
implementation(libs.okhttp)

// database query tracing
implementation(projects.sentryJdbc)
runtimeOnly(libs.hsqldb)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io.sentry.samples.spring.boot4.quartz.SampleJob;
import java.util.Collections;
import okhttp3.OkHttpClient;
import org.quartz.JobDetail;
import org.quartz.SimpleTrigger;
import org.springframework.boot.SpringApplication;
Expand Down Expand Up @@ -42,6 +43,12 @@ RestClient restClient(RestClient.Builder builder) {
return builder.build();
}

@Bean
OkHttpClient okHttpClient() {
// automatically instrumented by Sentry via sentry.clients.ok-http-enabled=true
return new OkHttpClient.Builder().build();
}

@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package io.sentry.samples.spring.boot4;

import io.sentry.reactor.SentryReactorUtils;
import java.io.IOException;
import java.io.UncheckedIOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -10,17 +15,27 @@
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import tools.jackson.databind.ObjectMapper;

@RestController
public class TodoController {
private final RestTemplate restTemplate;
private final WebClient webClient;
private final RestClient restClient;
private final OkHttpClient okHttpClient;
private final ObjectMapper objectMapper;

public TodoController(RestTemplate restTemplate, WebClient webClient, RestClient restClient) {
public TodoController(
RestTemplate restTemplate,
WebClient webClient,
RestClient restClient,
OkHttpClient okHttpClient,
ObjectMapper objectMapper) {
this.restTemplate = restTemplate;
this.webClient = webClient;
this.restClient = restClient;
this.okHttpClient = okHttpClient;
this.objectMapper = objectMapper;
}

@GetMapping("/todo/{id}")
Expand Down Expand Up @@ -54,4 +69,15 @@ Todo todoRestClient(@PathVariable Long id) {
.retrieve()
.body(Todo.class);
}

@GetMapping("/todo-okhttp/{id}")
Todo todoOkHttp(@PathVariable Long id) {
final Request request =
new Request.Builder().url("https://jsonplaceholder.typicode.com/todos/" + id).build();
try (Response response = okHttpClient.newCall(request).execute()) {
return objectMapper.readValue(response.body().byteStream(), Todo.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ sentry.profile-session-sample-rate=1.0
sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces
sentry.profile-lifecycle=TRACE
sentry.enable-cache-tracing=true
# Automatically instrument Spring-managed OkHttpClient beans
sentry.clients.ok-http-enabled=true
spring.cache.cache-names=todos
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ class TodoSystemTest {
)
}
}

@Test
fun `get todo okhttp works`() {
val restClient = testHelper.restClient
restClient.getTodoOkHttp(1L)
assertEquals(200, restClient.lastKnownStatusCode)

testHelper.ensureTransactionReceived { transaction, envelopeHeader ->
transaction.transaction == "GET /todo-okhttp/{id}" &&
testHelper.doesTransactionContainSpanWithOpAndDescription(
transaction,
"http.client",
"GET https://jsonplaceholder.typicode.com/todos/1",
)
}
}
}
8 changes: 8 additions & 0 deletions sentry-spring-boot-4/api/sentry-spring-boot-4.api
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class io/sentry/spring/boot4/SentryProfilerAutoConfiguration {

public class io/sentry/spring/boot4/SentryProperties : io/sentry/SentryOptions {
public fun <init> ()V
public fun getClients ()Lio/sentry/spring/boot4/SentryProperties$Clients;
public fun getExceptionResolverOrder ()I
public fun getGraphql ()Lio/sentry/spring/boot4/SentryProperties$Graphql;
public fun getLogging ()Lio/sentry/spring/boot4/SentryProperties$Logging;
Expand All @@ -49,6 +50,7 @@ public class io/sentry/spring/boot4/SentryProperties : io/sentry/SentryOptions {
public fun isEnableAotCompatibility ()Z
public fun isKeepTransactionsOpenForAsyncResponses ()Z
public fun isUseGitCommitIdAsRelease ()Z
public fun setClients (Lio/sentry/spring/boot4/SentryProperties$Clients;)V
public fun setEnableAotCompatibility (Z)V
public fun setExceptionResolverOrder (I)V
public fun setGraphql (Lio/sentry/spring/boot4/SentryProperties$Graphql;)V
Expand All @@ -59,6 +61,12 @@ public class io/sentry/spring/boot4/SentryProperties : io/sentry/SentryOptions {
public fun setUserFilterOrder (Ljava/lang/Integer;)V
}

public class io/sentry/spring/boot4/SentryProperties$Clients {
public fun <init> ()V
public fun isOkHttpEnabled ()Z
public fun setOkHttpEnabled (Z)V
}

public class io/sentry/spring/boot4/SentryProperties$Graphql {
public fun <init> ()V
public fun getIgnoredErrorTypes ()Ljava/util/List;
Expand Down
12 changes: 8 additions & 4 deletions sentry-spring-boot-4/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ dependencies {
compileOnly(projects.sentryLogback)
compileOnly(projects.sentryLog4j2)
compileOnly(projects.sentryApacheHttpClient5)
compileOnly(libs.log4j.api)
compileOnly(libs.log4j.core)
compileOnly(platform(SpringBootPlugin.BOM_COORDINATES))
compileOnly(projects.sentryGraphql)
compileOnly(projects.sentryGraphql22)
Expand All @@ -44,6 +42,10 @@ dependencies {
compileOnly(libs.context.propagation)
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nopen.annotations)
compileOnly(libs.log4j.api)
compileOnly(libs.log4j.core)
compileOnly(projects.sentryOkhttp)
compileOnly(libs.okhttp)
compileOnly(libs.otel)
compileOnly(libs.reactor.core)
compileOnly(libs.servlet.jakarta.api)
Expand All @@ -69,9 +71,8 @@ dependencies {
// tests
testImplementation(projects.sentryLogback)
testImplementation(projects.sentryLog4j2)
testImplementation(projects.sentryOkhttp)
testImplementation(projects.sentryApacheHttpClient5)
testImplementation(libs.log4j.api)
testImplementation(libs.log4j.core)
testImplementation(projects.sentryGraphql)
testImplementation(projects.sentryGraphql22)
testImplementation(projects.sentryKafka)
Expand All @@ -87,8 +88,11 @@ dependencies {
testImplementation(platform(SpringBootPlugin.BOM_COORDINATES))
testImplementation(libs.context.propagation)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.google.truth)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.okhttp)
testImplementation(libs.log4j.api)
testImplementation(libs.log4j.core)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.otel)
testImplementation(libs.otel.extension.autoconfigure.spi)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,28 @@ static class SentryKafkaQueueConfiguration {
}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(
name = {
"okhttp3.OkHttpClient",
"io.sentry.okhttp.SentryOkHttpInterceptor",
"io.sentry.okhttp.SentryOkHttpEventListener"
})
@ConditionalOnProperty(name = "sentry.clients.ok-http-enabled", havingValue = "true")
@ConditionalOnMissingClass({
"io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider",
"io.sentry.opentelemetry.agent.AgentMarker"
})
@Open
static class SentryOkHttpConfiguration {

@Bean
public static @NotNull SentryOkHttpClientBeanPostProcessor
sentryOkHttpClientBeanPostProcessor() {
return new SentryOkHttpClientBeanPostProcessor();
}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ProceedingJoinPoint.class)
@ConditionalOnProperty(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.sentry.spring.boot4;

import io.sentry.ScopesAdapter;
import io.sentry.SentryLevel;
import io.sentry.okhttp.SentryOkHttpEventListener;
import io.sentry.okhttp.SentryOkHttpInterceptor;
import okhttp3.Call;
import okhttp3.EventListener;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;

final class SentryOkHttpClientBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {

@Override
public @NotNull Object postProcessAfterInitialization(
final @NotNull Object bean, final @NotNull String beanName) throws BeansException {
if (!(bean instanceof OkHttpClient)) {
return bean;
}

final @NotNull OkHttpClient client = (OkHttpClient) bean;
if (client.getClass() != OkHttpClient.class) {
ScopesAdapter.getInstance()
.getOptions()
.getLogger()
.log(
SentryLevel.WARNING,
"Sentry OkHttp auto-instrumentation skipped for bean '%s' (%s) because replacing "
+ "an OkHttpClient subclass would not preserve its type. Configure Sentry "
+ "instrumentation manually for this client.",
beanName,
client.getClass().getName());
return client;
}

final boolean addInterceptor = !hasSentryInterceptor(client);
final boolean wrapEventListener =
!(client.eventListenerFactory() instanceof SentryEventListenerFactory);
Comment thread
cursor[bot] marked this conversation as resolved.
if (!addInterceptor && !wrapEventListener) {
return client;
}

final @NotNull OkHttpClient.Builder builder = client.newBuilder();
if (addInterceptor) {
builder.addInterceptor(new SentryOkHttpInterceptor());
}
if (wrapEventListener) {
builder.eventListenerFactory(new SentryEventListenerFactory(client.eventListenerFactory()));
}
return builder.build();
}

private static boolean hasSentryInterceptor(final @NotNull OkHttpClient client) {
for (final @NotNull Interceptor interceptor : client.interceptors()) {
if (interceptor instanceof SentryOkHttpInterceptor) {
return true;
}
}
return false;
}

@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}

private static final class SentryEventListenerFactory implements EventListener.Factory {
private final @NotNull EventListener.Factory delegate;

private SentryEventListenerFactory(final @NotNull EventListener.Factory delegate) {
this.delegate = delegate;
}

@Override
public @NotNull EventListener create(final @NotNull Call call) {
final @NotNull EventListener original = delegate.create(call);
if (original instanceof SentryOkHttpEventListener) {
return original;
}
return new SentryOkHttpEventListener(ScopesAdapter.getInstance(), original);
}
Comment thread
cursor[bot] marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public class SentryProperties extends SentryOptions {
/** Graphql integration properties. */
private @NotNull Graphql graphql = new Graphql();

/** Clients integration properties. */
private @NotNull Clients clients = new Clients();

public boolean isUseGitCommitIdAsRelease() {
return useGitCommitIdAsRelease;
}
Expand Down Expand Up @@ -124,6 +127,14 @@ public void setGraphql(@NotNull Graphql graphql) {
this.graphql = graphql;
}

public @NotNull Clients getClients() {
return clients;
}

public void setClients(@NotNull Clients clients) {
this.clients = clients;
}

@Open
public static class Logging {
/** Enable/Disable logging auto-configuration. */
Expand Down Expand Up @@ -215,4 +226,18 @@ public void setIgnoredErrorTypes(final @NotNull List<String> ignoredErrorTypes)
this.ignoredErrorTypes = ignoredErrorTypes;
}
}

@Open
public static class Clients {
/** Enable automatic instrumentation of Spring-managed OkHttp clients. Disabled by default. */
private boolean okHttpEnabled = false;

public boolean isOkHttpEnabled() {
return okHttpEnabled;
}

public void setOkHttpEnabled(boolean okHttpEnabled) {
this.okHttpEnabled = okHttpEnabled;
}
}
}
Loading
Loading