Skip to content

Commit 2065d35

Browse files
adinauerlbloder
andauthored
Support graphql-java v22 in Sentry Spring (Boot) integrations (#3745)
* attach request body for application/x-www-form-urlencoded * extend tests * changelog * Add support for v22 of graphql-java, new modules sentry-graphql-22 and sentry-graphql-core * Add back callback interface and constants as deprecated * Replace GraphQlSourceBuilderCustomizer with directly providing a SentryInstrumentation bean if missing * Support graphql-java v22 in spring integrations * add auto configuration tests for graphql * format * another test --------- Co-authored-by: Lukas Bloder <[email protected]>
1 parent 093ebc6 commit 2065d35

File tree

7 files changed

+208
-2
lines changed

7 files changed

+208
-2
lines changed

sentry-spring-boot-jakarta/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030
compileOnly(Config.Libs.springBoot3Starter)
3131
compileOnly(platform(SpringBootPlugin.BOM_COORDINATES))
3232
compileOnly(projects.sentryGraphql)
33+
compileOnly(projects.sentryGraphql22)
3334
compileOnly(projects.sentryQuartz)
3435
compileOnly(Config.Libs.springWeb)
3536
compileOnly(Config.Libs.springWebflux)
@@ -55,6 +56,8 @@ dependencies {
5556
// tests
5657
testImplementation(projects.sentryLogback)
5758
testImplementation(projects.sentryQuartz)
59+
testImplementation(projects.sentryGraphql)
60+
testImplementation(projects.sentryGraphql22)
5861
testImplementation(projects.sentryApacheHttpClient5)
5962
testImplementation(projects.sentryTestSupport)
6063
testImplementation(kotlin(Config.kotlinStdLib))
@@ -71,6 +74,7 @@ dependencies {
7174
testImplementation(Config.Libs.springBoot3StarterSecurity)
7275
testImplementation(Config.Libs.springBoot3StarterAop)
7376
testImplementation(Config.Libs.springBoot3StarterQuartz)
77+
testImplementation(Config.Libs.springBoot3StarterGraphql)
7478
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore)
7579
testImplementation(Config.Libs.contextPropagation)
7680
}

sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
import io.sentry.Sentry;
1212
import io.sentry.SentryIntegrationPackageStorage;
1313
import io.sentry.SentryOptions;
14-
import io.sentry.graphql.SentryGraphqlExceptionHandler;
1514
import io.sentry.protocol.SdkVersion;
1615
import io.sentry.quartz.SentryJobListener;
16+
import io.sentry.spring.boot.jakarta.graphql.SentryGraphql22AutoConfiguration;
1717
import io.sentry.spring.boot.jakarta.graphql.SentryGraphqlAutoConfiguration;
1818
import io.sentry.spring.jakarta.ContextTagsEventProcessor;
1919
import io.sentry.spring.jakarta.SentryExceptionResolver;
@@ -176,12 +176,25 @@ static class OpenTelemetryLinkErrorEventProcessorConfiguration {
176176
@Import(SentryGraphqlAutoConfiguration.class)
177177
@Open
178178
@ConditionalOnClass({
179-
SentryGraphqlExceptionHandler.class,
179+
io.sentry.graphql.SentryInstrumentation.class,
180180
DataFetcherExceptionResolverAdapter.class,
181181
GraphQLError.class
182182
})
183+
@ConditionalOnMissingClass({
184+
"io.sentry.graphql22.SentryInstrumentation" // avoid duplicate bean
185+
})
183186
static class GraphqlConfiguration {}
184187

188+
@Configuration(proxyBeanMethods = false)
189+
@Import(SentryGraphql22AutoConfiguration.class)
190+
@Open
191+
@ConditionalOnClass({
192+
io.sentry.graphql22.SentryInstrumentation.class,
193+
DataFetcherExceptionResolverAdapter.class,
194+
GraphQLError.class
195+
})
196+
static class Graphql22Configuration {}
197+
185198
@Configuration(proxyBeanMethods = false)
186199
@Import(SentryQuartzConfiguration.class)
187200
@Open
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.sentry.spring.boot.jakarta.graphql;
2+
3+
import com.jakewharton.nopen.annotation.Open;
4+
import io.sentry.SentryIntegrationPackageStorage;
5+
import io.sentry.graphql.SentryGraphqlInstrumentation;
6+
import io.sentry.graphql22.SentryInstrumentation;
7+
import io.sentry.spring.boot.jakarta.SentryProperties;
8+
import io.sentry.spring.jakarta.graphql.SentryDataFetcherExceptionResolverAdapter;
9+
import io.sentry.spring.jakarta.graphql.SentryGraphqlBeanPostProcessor;
10+
import io.sentry.spring.jakarta.graphql.SentrySpringSubscriptionHandler;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.springframework.beans.factory.ObjectProvider;
13+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
14+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
15+
import org.springframework.context.annotation.Bean;
16+
import org.springframework.context.annotation.Configuration;
17+
import org.springframework.core.Ordered;
18+
import org.springframework.core.annotation.Order;
19+
20+
@Configuration(proxyBeanMethods = false)
21+
@Open
22+
public class SentryGraphql22AutoConfiguration {
23+
24+
@Bean(name = "sentryInstrumentation")
25+
@ConditionalOnMissingBean(name = "sentryInstrumentation")
26+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
27+
public SentryInstrumentation sentryInstrumentationWebMvc(
28+
final @NotNull SentryProperties sentryProperties,
29+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
30+
beforeSpanCallback) {
31+
SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebMVC");
32+
return createInstrumentation(sentryProperties, beforeSpanCallback, false);
33+
}
34+
35+
@Bean(name = "sentryInstrumentation")
36+
@ConditionalOnMissingBean(name = "sentryInstrumentation")
37+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
38+
public SentryInstrumentation sentryInstrumentationWebflux(
39+
final @NotNull SentryProperties sentryProperties,
40+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
41+
beforeSpanCallback) {
42+
SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebFlux");
43+
return createInstrumentation(sentryProperties, beforeSpanCallback, true);
44+
}
45+
46+
/**
47+
* We're not setting defaultDataFetcherExceptionHandler here on purpose and instead use the
48+
* resolver adapter below. This way Springs handler can still forward to other resolver adapters.
49+
*/
50+
private SentryInstrumentation createInstrumentation(
51+
final @NotNull SentryProperties sentryProperties,
52+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
53+
beforeSpanCallback,
54+
final boolean captureRequestBody) {
55+
return new SentryInstrumentation(
56+
beforeSpanCallback.getIfAvailable(),
57+
new SentrySpringSubscriptionHandler(),
58+
captureRequestBody,
59+
sentryProperties.getGraphql().getIgnoredErrorTypes());
60+
}
61+
62+
@Bean
63+
@Order(Ordered.HIGHEST_PRECEDENCE)
64+
public SentryDataFetcherExceptionResolverAdapter exceptionResolverAdapter() {
65+
return new SentryDataFetcherExceptionResolverAdapter();
66+
}
67+
68+
@Bean
69+
public static SentryGraphqlBeanPostProcessor graphqlBeanPostProcessor() {
70+
return new SentryGraphqlBeanPostProcessor();
71+
}
72+
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,50 @@ class SentryAutoConfigurationTest {
835835
}
836836
}
837837

838+
@Test
839+
fun `does not create any graphql config if no sentry-graphql lib on classpath`() {
840+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
841+
.withClassLoader(
842+
FilteredClassLoader(
843+
io.sentry.graphql.SentryInstrumentation::class.java,
844+
io.sentry.graphql22.SentryInstrumentation::class.java
845+
)
846+
)
847+
.run {
848+
assertThat(it).doesNotHaveBean(io.sentry.graphql.SentryInstrumentation::class.java)
849+
assertThat(it).doesNotHaveBean(io.sentry.graphql22.SentryInstrumentation::class.java)
850+
}
851+
}
852+
853+
@Test
854+
fun `sentry-graphql22 configuration takes precedence over sentry-graphql if both on classpath`() {
855+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
856+
.run {
857+
assertThat(it).hasSingleBean(io.sentry.graphql22.SentryInstrumentation::class.java)
858+
assertThat(it).doesNotHaveBean(io.sentry.graphql.SentryInstrumentation::class.java)
859+
}
860+
}
861+
862+
@Test
863+
fun `sentry graphql configuration is created if graphql22 not on classpath`() {
864+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
865+
.withClassLoader(FilteredClassLoader(io.sentry.graphql22.SentryInstrumentation::class.java))
866+
.run {
867+
assertThat(it).hasSingleBean(io.sentry.graphql.SentryInstrumentation::class.java)
868+
assertThat(it).doesNotHaveBean(io.sentry.graphql22.SentryInstrumentation::class.java)
869+
}
870+
}
871+
872+
@Test
873+
fun `sentry graphql22 configuration is created if graphql not on classpath`() {
874+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
875+
.withClassLoader(FilteredClassLoader(io.sentry.graphql.SentryInstrumentation::class.java))
876+
.run {
877+
assertThat(it).doesNotHaveBean(io.sentry.graphql.SentryInstrumentation::class.java)
878+
assertThat(it).hasSingleBean(io.sentry.graphql22.SentryInstrumentation::class.java)
879+
}
880+
}
881+
838882
@Test
839883
fun `Sentry quartz job listener is added`() {
840884
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true")

sentry-spring-jakarta/api/sentry-spring-jakarta.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ public final class io/sentry/spring/jakarta/graphql/SentryDgsSubscriptionHandler
175175
public fun onSubscriptionResult (Ljava/lang/Object;Lio/sentry/IScopes;Lio/sentry/graphql/ExceptionReporter;Lgraphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters;)Ljava/lang/Object;
176176
}
177177

178+
public class io/sentry/spring/jakarta/graphql/SentryGraphql22Configuration {
179+
public fun <init> ()V
180+
public fun exceptionResolverAdapter ()Lio/sentry/spring/jakarta/graphql/SentryDataFetcherExceptionResolverAdapter;
181+
public fun graphqlBeanPostProcessor ()Lio/sentry/spring/jakarta/graphql/SentryGraphqlBeanPostProcessor;
182+
public fun sentryInstrumentationWebMvc (Lorg/springframework/beans/factory/ObjectProvider;)Lio/sentry/graphql22/SentryInstrumentation;
183+
public fun sentryInstrumentationWebflux (Lorg/springframework/beans/factory/ObjectProvider;)Lio/sentry/graphql22/SentryInstrumentation;
184+
}
185+
178186
public final class io/sentry/spring/jakarta/graphql/SentryGraphqlBeanPostProcessor : org/springframework/beans/factory/config/BeanPostProcessor, org/springframework/core/PriorityOrdered {
179187
public fun <init> ()V
180188
public fun getOrder ()I

sentry-spring-jakarta/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
errorprone(Config.CompileOnly.errorProneNullAway)
4545
compileOnly(Config.CompileOnly.jetbrainsAnnotations)
4646
compileOnly(projects.sentryGraphql)
47+
compileOnly(projects.sentryGraphql22)
4748
compileOnly(projects.sentryQuartz)
4849

4950
// tests
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.sentry.spring.jakarta.graphql;
2+
3+
import com.jakewharton.nopen.annotation.Open;
4+
import io.sentry.SentryIntegrationPackageStorage;
5+
import io.sentry.graphql.SentryGraphqlInstrumentation;
6+
import io.sentry.graphql22.SentryInstrumentation;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.springframework.beans.factory.ObjectProvider;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
11+
import org.springframework.context.annotation.Bean;
12+
import org.springframework.context.annotation.Configuration;
13+
import org.springframework.core.Ordered;
14+
import org.springframework.core.annotation.Order;
15+
16+
@Configuration(proxyBeanMethods = false)
17+
@Open
18+
public class SentryGraphql22Configuration {
19+
20+
@Bean(name = "sentryInstrumentation")
21+
@ConditionalOnMissingBean(name = "sentryInstrumentation")
22+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
23+
public SentryInstrumentation sentryInstrumentationWebMvc(
24+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
25+
beforeSpanCallback) {
26+
SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebMVC");
27+
return createInstrumentation(beforeSpanCallback, false);
28+
}
29+
30+
@Bean(name = "sentryInstrumentation")
31+
@ConditionalOnMissingBean(name = "sentryInstrumentation")
32+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
33+
public SentryInstrumentation sentryInstrumentationWebflux(
34+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
35+
beforeSpanCallback) {
36+
SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebFlux");
37+
return createInstrumentation(beforeSpanCallback, true);
38+
}
39+
40+
/**
41+
* We're not setting defaultDataFetcherExceptionHandler here on purpose and instead use the
42+
* resolver adapter below. This way Springs handler can still forward to other resolver adapters.
43+
*/
44+
private SentryInstrumentation createInstrumentation(
45+
final @NotNull ObjectProvider<SentryGraphqlInstrumentation.BeforeSpanCallback>
46+
beforeSpanCallback,
47+
final boolean captureRequestBody) {
48+
return new SentryInstrumentation(
49+
beforeSpanCallback.getIfAvailable(),
50+
new SentrySpringSubscriptionHandler(),
51+
captureRequestBody);
52+
}
53+
54+
@Bean
55+
@Order(Ordered.HIGHEST_PRECEDENCE)
56+
public SentryDataFetcherExceptionResolverAdapter exceptionResolverAdapter() {
57+
return new SentryDataFetcherExceptionResolverAdapter();
58+
}
59+
60+
@Bean
61+
public SentryGraphqlBeanPostProcessor graphqlBeanPostProcessor() {
62+
return new SentryGraphqlBeanPostProcessor();
63+
}
64+
}

0 commit comments

Comments
 (0)