From e655f20f14e4abef53b9067a1a083955f3248d6d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 5 Oct 2023 08:38:08 +0200 Subject: [PATCH 1/2] Allow filtering graphql errors --- sentry-graphql/api/sentry-graphql.api | 3 +- .../sentry/graphql/SentryInstrumentation.java | 42 ++++++++++-- .../SentryInstrumentationAnotherTest.kt | 22 ++++++- .../graphql/SentryInstrumentationTest.kt | 2 +- .../api/sentry-spring-boot-jakarta.api | 16 +++++ .../boot/jakarta/SentryAutoConfiguration.java | 4 +- .../spring/boot/jakarta/SentryProperties.java | 28 ++++++++ .../SentryGraphqlAutoConfiguration.java | 65 +++++++++++++++++++ sentry-spring-boot/api/sentry-spring-boot.api | 16 +++++ .../spring/boot/SentryAutoConfiguration.java | 4 +- .../sentry/spring/boot/SentryProperties.java | 28 ++++++++ .../SentryGraphqlAutoConfiguration.java | 65 +++++++++++++++++++ 12 files changed, 283 insertions(+), 12 deletions(-) create mode 100644 sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration.java create mode 100644 sentry-spring-boot/src/main/java/io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration.java diff --git a/sentry-graphql/api/sentry-graphql.api b/sentry-graphql/api/sentry-graphql.api index 5d4b907f146..58e864d0b48 100644 --- a/sentry-graphql/api/sentry-graphql.api +++ b/sentry-graphql/api/sentry-graphql.api @@ -53,8 +53,9 @@ public final class io/sentry/graphql/SentryInstrumentation : graphql/execution/i public fun (Lio/sentry/IHub;)V public fun (Lio/sentry/IHub;Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;)V public fun (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;)V - public fun (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;)V + public fun (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V public fun (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V + public fun (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V public fun (Lio/sentry/graphql/SentrySubscriptionHandler;Z)V public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;)Lgraphql/execution/instrumentation/InstrumentationContext; public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;)Lgraphql/execution/instrumentation/InstrumentationContext; diff --git a/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java b/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java index c5f36d7228e..8a1a9829e27 100644 --- a/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java +++ b/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java @@ -26,6 +26,7 @@ import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SpanStatus; import io.sentry.util.StringUtils; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -52,6 +53,8 @@ public final class SentryInstrumentation extends SimpleInstrumentation { private final @NotNull ExceptionReporter exceptionReporter; + private final @NotNull List ignoredErrorTypes; + /** * @deprecated please use a constructor that takes a {@link SentrySubscriptionHandler} instead. */ @@ -104,17 +107,40 @@ public SentryInstrumentation( this( beforeSpan, subscriptionHandler, - new ExceptionReporter(captureRequestBodyForNonSubscriptions)); + new ExceptionReporter(captureRequestBodyForNonSubscriptions), + new ArrayList<>()); + } + + /** + * @param beforeSpan callback when a span is created + * @param subscriptionHandler can report subscription errors + * @param captureRequestBodyForNonSubscriptions false if request bodies should not be captured by + * this integration for query and mutation operations. This can be used to prevent unnecessary + * work by not adding the request body when another integration will add it anyways, as is the + * case with our spring integration for WebMVC. + */ + public SentryInstrumentation( + final @Nullable BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final boolean captureRequestBodyForNonSubscriptions, + final @NotNull List ignoredErrorTypes) { + this( + beforeSpan, + subscriptionHandler, + new ExceptionReporter(captureRequestBodyForNonSubscriptions), + ignoredErrorTypes); } @TestOnly public SentryInstrumentation( final @Nullable BeforeSpanCallback beforeSpan, final @NotNull SentrySubscriptionHandler subscriptionHandler, - final @NotNull ExceptionReporter exceptionReporter) { + final @NotNull ExceptionReporter exceptionReporter, + final @NotNull List ignoredErrorTypes) { this.beforeSpan = beforeSpan; this.subscriptionHandler = subscriptionHandler; this.exceptionReporter = exceptionReporter; + this.ignoredErrorTypes = ignoredErrorTypes; SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL"); SentryIntegrationPackageStorage.getInstance() .addPackage("maven:io.sentry:sentry-graphql", BuildConfig.VERSION_NAME); @@ -174,8 +200,7 @@ public CompletableFuture instrumentExecutionResult( // not capturing INTERNAL_ERRORS as they should be reported via graphQlContext // above String errorType = getErrorType(error); - if (errorType == null - || !ERROR_TYPES_HANDLED_BY_DATA_FETCHERS.contains(errorType)) { + if (!isIgnored(errorType)) { exceptionReporter.captureThrowable( new RuntimeException(error.getMessage()), new ExceptionReporter.ExceptionDetails( @@ -195,6 +220,15 @@ public CompletableFuture instrumentExecutionResult( }); } + private boolean isIgnored(final @Nullable String errorType) { + if (errorType == null) { + return false; + } + + return ERROR_TYPES_HANDLED_BY_DATA_FETCHERS.contains(errorType) + || ignoredErrorTypes.contains(errorType); + } + private @Nullable String getErrorType(final @Nullable GraphQLError error) { if (error == null) { return null; diff --git a/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationAnotherTest.kt b/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationAnotherTest.kt index 2b78036ef37..b8bc76388c6 100644 --- a/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationAnotherTest.kt +++ b/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationAnotherTest.kt @@ -1,5 +1,6 @@ package io.sentry.graphql +import graphql.ErrorClassification import graphql.ErrorType import graphql.ExecutionInput import graphql.ExecutionResultImpl @@ -68,7 +69,7 @@ class SentryInstrumentationAnotherTest { val query = """query greeting(name: "somename")""" val variables = mapOf("variableA" to "value a") - fun getSut(isTransactionActive: Boolean = true, operation: OperationDefinition.Operation = OperationDefinition.Operation.QUERY, graphQLContextParam: Map? = null, addTransactionToTracingState: Boolean = true): SentryInstrumentation { + fun getSut(isTransactionActive: Boolean = true, operation: OperationDefinition.Operation = OperationDefinition.Operation.QUERY, graphQLContextParam: Map? = null, addTransactionToTracingState: Boolean = true, ignoredErrors: List = emptyList()): SentryInstrumentation { whenever(hub.options).thenReturn(SentryOptions()) activeSpan = SentryTracer(TransactionContext("name", "op"), hub) @@ -86,7 +87,7 @@ class SentryInstrumentationAnotherTest { exceptionReporter = mock() subscriptionHandler = mock() whenever(subscriptionHandler.onSubscriptionResult(any(), any(), any(), any())).thenReturn("result modified by subscription handler") - val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter) + val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter, ignoredErrors) dataFetcher = mock>() whenever(dataFetcher.get(any())).thenReturn("raw result") graphQLContext = GraphQLContext.newContext() @@ -325,6 +326,19 @@ class SentryInstrumentationAnotherTest { assertSame(executionResult, result) } + @Test + fun `does not invoke exceptionReporter for ignored errors`() { + val instrumentation = fixture.getSut(ignoredErrors = listOf("SOME_ERROR")) + val executionResult = ExecutionResultImpl.newExecutionResult() + .data("raw result") + .addError(GraphqlErrorException.newErrorException().message("exception message").errorClassification(SomeErrorClassification.SOME_ERROR).build()) + .build() + val resultFuture = instrumentation.instrumentExecutionResult(executionResult, fixture.instrumentationExecutionParameters) + verify(fixture.exceptionReporter, never()).captureThrowable(any(), any(), any()) + val result = resultFuture.get() + assertSame(executionResult, result) + } + @Test fun `never invokes exceptionReporter if no errors`() { val instrumentation = fixture.getSut() @@ -343,4 +357,8 @@ class SentryInstrumentationAnotherTest { } data class Show(val id: Int) + + enum class SomeErrorClassification : ErrorClassification { + SOME_ERROR; + } } diff --git a/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationTest.kt b/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationTest.kt index 19b7b3732b8..2294a6aa9c8 100644 --- a/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationTest.kt +++ b/sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationTest.kt @@ -170,7 +170,7 @@ class SentryInstrumentationTest { val subscriptionHandler = mock() whenever(subscriptionHandler.onSubscriptionResult(any(), any(), any(), any())).thenReturn("result modified by subscription handler") val operation = OperationDefinition.Operation.SUBSCRIPTION - val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter) + val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter, emptyList()) val dataFetcher = mock>() whenever(dataFetcher.get(any())).thenReturn("raw result") val graphQLContext = GraphQLContext.newContext().build() diff --git a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api index 8611bbf0a2d..566ef2b30e1 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -21,6 +21,7 @@ public class io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfiguratio public class io/sentry/spring/boot/jakarta/SentryProperties : io/sentry/SentryOptions { public fun ()V public fun getExceptionResolverOrder ()I + public fun getGraphql ()Lio/sentry/spring/boot/jakarta/SentryProperties$Graphql; public fun getLogging ()Lio/sentry/spring/boot/jakarta/SentryProperties$Logging; public fun getReactive ()Lio/sentry/spring/boot/jakarta/SentryProperties$Reactive; public fun getUserFilterOrder ()Ljava/lang/Integer; @@ -28,12 +29,19 @@ public class io/sentry/spring/boot/jakarta/SentryProperties : io/sentry/SentryOp public fun isUseGitCommitIdAsRelease ()Z public fun setEnableAotCompatibility (Z)V public fun setExceptionResolverOrder (I)V + public fun setGraphql (Lio/sentry/spring/boot/jakarta/SentryProperties$Graphql;)V public fun setLogging (Lio/sentry/spring/boot/jakarta/SentryProperties$Logging;)V public fun setReactive (Lio/sentry/spring/boot/jakarta/SentryProperties$Reactive;)V public fun setUseGitCommitIdAsRelease (Z)V public fun setUserFilterOrder (Ljava/lang/Integer;)V } +public class io/sentry/spring/boot/jakarta/SentryProperties$Graphql { + public fun ()V + public fun getIgnoredErrorTypes ()Ljava/util/List; + public fun setIgnoredErrorTypes (Ljava/util/List;)V +} + public class io/sentry/spring/boot/jakarta/SentryProperties$Logging { public fun ()V public fun getLoggers ()Ljava/util/List; @@ -57,3 +65,11 @@ public class io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration { public fun sentryWebExceptionHandler (Lio/sentry/IHub;)Lio/sentry/spring/jakarta/webflux/SentryWebExceptionHandler; } +public class io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration { + public fun ()V + public fun exceptionResolverAdapter ()Lio/sentry/spring/jakarta/graphql/SentryDataFetcherExceptionResolverAdapter; + public fun graphqlBeanPostProcessor ()Lio/sentry/spring/jakarta/graphql/SentryGraphqlBeanPostProcessor; + public fun sourceBuilderCustomizerWebflux (Lio/sentry/spring/boot/jakarta/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer; + public fun sourceBuilderCustomizerWebmvc (Lio/sentry/spring/boot/jakarta/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer; +} + diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index 3ebcfc8bb9f..ddec6717002 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -14,6 +14,7 @@ import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; +import io.sentry.spring.boot.jakarta.graphql.SentryGraphqlAutoConfiguration; import io.sentry.spring.jakarta.ContextTagsEventProcessor; import io.sentry.spring.jakarta.SentryExceptionResolver; import io.sentry.spring.jakarta.SentryRequestResolver; @@ -25,7 +26,6 @@ import io.sentry.spring.jakarta.checkin.SentryCheckInAdviceConfiguration; import io.sentry.spring.jakarta.checkin.SentryCheckInPointcutConfiguration; import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration; -import io.sentry.spring.jakarta.graphql.SentryGraphqlConfiguration; import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration; import io.sentry.spring.jakarta.tracing.SentrySpanPointcutConfiguration; import io.sentry.spring.jakarta.tracing.SentryTracingFilter; @@ -164,7 +164,7 @@ static class OpenTelemetryLinkErrorEventProcessorConfiguration { } @Configuration(proxyBeanMethods = false) - @Import(SentryGraphqlConfiguration.class) + @Import(SentryGraphqlAutoConfiguration.class) @Open @ConditionalOnClass({ SentryGraphqlExceptionHandler.class, diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java index f5c485b402b..7b3469d7f1f 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java @@ -2,6 +2,7 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.SentryOptions; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -40,6 +41,9 @@ public class SentryProperties extends SentryOptions { */ private boolean enableAotCompatibility = false; + /** Graphql integration properties. */ + private @NotNull Graphql graphql = new Graphql(); + public boolean isUseGitCommitIdAsRelease() { return useGitCommitIdAsRelease; } @@ -100,6 +104,14 @@ public void setEnableAotCompatibility(boolean enableAotCompatibility) { this.enableAotCompatibility = enableAotCompatibility; } + public @NotNull Graphql getGraphql() { + return graphql; + } + + public void setGraphql(@NotNull Graphql graphql) { + this.graphql = graphql; + } + @Open public static class Logging { /** Enable/Disable logging auto-configuration. */ @@ -163,4 +175,20 @@ public void setThreadLocalAccessorEnabled(boolean threadLocalAccessorEnabled) { this.threadLocalAccessorEnabled = threadLocalAccessorEnabled; } } + + @Open + public static class Graphql { + + /** List of error types the Sentry Graphql integration should ignore. */ + private @NotNull List ignoredErrorTypes = new ArrayList<>(); + + @NotNull + public List getIgnoredErrorTypes() { + return ignoredErrorTypes; + } + + public void setIgnoredErrorTypes(final @NotNull List ignoredErrorTypes) { + this.ignoredErrorTypes = ignoredErrorTypes; + } + } } diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration.java new file mode 100644 index 00000000000..0fcbdec072d --- /dev/null +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration.java @@ -0,0 +1,65 @@ +package io.sentry.spring.boot.jakarta.graphql; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.graphql.SentryInstrumentation; +import io.sentry.spring.boot.jakarta.SentryProperties; +import io.sentry.spring.jakarta.graphql.SentryDataFetcherExceptionResolverAdapter; +import io.sentry.spring.jakarta.graphql.SentryGraphqlBeanPostProcessor; +import io.sentry.spring.jakarta.graphql.SentrySpringSubscriptionHandler; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +@Configuration(proxyBeanMethods = false) +@Open +public class SentryGraphqlAutoConfiguration { + + @Bean + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + public GraphQlSourceBuilderCustomizer sourceBuilderCustomizerWebmvc( + final @NotNull SentryProperties sentryProperties) { + SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebMVC"); + return sourceBuilderCustomizer(sentryProperties, false); + } + + @Bean + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + public GraphQlSourceBuilderCustomizer sourceBuilderCustomizerWebflux( + final @NotNull SentryProperties sentryProperties) { + SentryIntegrationPackageStorage.getInstance().addIntegration("Spring6GrahQLWebFlux"); + return sourceBuilderCustomizer(sentryProperties, true); + } + + /** + * We're not setting defaultDataFetcherExceptionHandler here on purpose and instead use the + * resolver adapter below. This way Springs handler can still forward to other resolver adapters. + */ + private GraphQlSourceBuilderCustomizer sourceBuilderCustomizer( + final @NotNull SentryProperties sentryProperties, final boolean captureRequestBody) { + return (builder) -> + builder.configureGraphQl( + graphQlBuilder -> + graphQlBuilder.instrumentation( + new SentryInstrumentation( + null, + new SentrySpringSubscriptionHandler(), + captureRequestBody, + sentryProperties.getGraphql().getIgnoredErrorTypes()))); + } + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentryDataFetcherExceptionResolverAdapter exceptionResolverAdapter() { + return new SentryDataFetcherExceptionResolverAdapter(); + } + + @Bean + public SentryGraphqlBeanPostProcessor graphqlBeanPostProcessor() { + return new SentryGraphqlBeanPostProcessor(); + } +} diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index 642f550e6ba..91266f9d986 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -21,15 +21,23 @@ public class io/sentry/spring/boot/SentryLogbackAppenderAutoConfiguration { public class io/sentry/spring/boot/SentryProperties : io/sentry/SentryOptions { public fun ()V public fun getExceptionResolverOrder ()I + public fun getGraphql ()Lio/sentry/spring/boot/SentryProperties$Graphql; public fun getLogging ()Lio/sentry/spring/boot/SentryProperties$Logging; public fun getUserFilterOrder ()Ljava/lang/Integer; public fun isUseGitCommitIdAsRelease ()Z public fun setExceptionResolverOrder (I)V + public fun setGraphql (Lio/sentry/spring/boot/SentryProperties$Graphql;)V public fun setLogging (Lio/sentry/spring/boot/SentryProperties$Logging;)V public fun setUseGitCommitIdAsRelease (Z)V public fun setUserFilterOrder (Ljava/lang/Integer;)V } +public class io/sentry/spring/boot/SentryProperties$Graphql { + public fun ()V + public fun getIgnoredErrorTypes ()Ljava/util/List; + public fun setIgnoredErrorTypes (Ljava/util/List;)V +} + public class io/sentry/spring/boot/SentryProperties$Logging { public fun ()V public fun getLoggers ()Ljava/util/List; @@ -49,3 +57,11 @@ public class io/sentry/spring/boot/SentryWebfluxAutoConfiguration { public fun sentryWebFilter (Lio/sentry/IHub;)Lio/sentry/spring/webflux/SentryWebFilter; } +public class io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration { + public fun ()V + public fun exceptionResolverAdapter ()Lio/sentry/spring/graphql/SentryDataFetcherExceptionResolverAdapter; + public fun graphqlBeanPostProcessor ()Lio/sentry/spring/graphql/SentryGraphqlBeanPostProcessor; + public fun sourceBuilderCustomizerWebflux (Lio/sentry/spring/boot/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer; + public fun sourceBuilderCustomizerWebmvc (Lio/sentry/spring/boot/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer; +} + diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 131be4a1ecb..2c6a330f456 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -22,10 +22,10 @@ import io.sentry.spring.SentryUserProvider; import io.sentry.spring.SentryWebConfiguration; import io.sentry.spring.SpringSecuritySentryUserProvider; +import io.sentry.spring.boot.graphql.SentryGraphqlAutoConfiguration; import io.sentry.spring.checkin.SentryCheckInAdviceConfiguration; import io.sentry.spring.checkin.SentryCheckInPointcutConfiguration; import io.sentry.spring.checkin.SentryQuartzConfiguration; -import io.sentry.spring.graphql.SentryGraphqlConfiguration; import io.sentry.spring.tracing.SentryAdviceConfiguration; import io.sentry.spring.tracing.SentrySpanPointcutConfiguration; import io.sentry.spring.tracing.SentryTracingFilter; @@ -164,7 +164,7 @@ static class OpenTelemetryLinkErrorEventProcessorConfiguration { } @Configuration(proxyBeanMethods = false) - @Import(SentryGraphqlConfiguration.class) + @Import(SentryGraphqlAutoConfiguration.class) @Open @ConditionalOnClass({ SentryGraphqlExceptionHandler.class, diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java index 6172966a74b..334e36f4024 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java @@ -2,6 +2,7 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.SentryOptions; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -30,6 +31,9 @@ public class SentryProperties extends SentryOptions { /** Logging framework integration properties. */ private @NotNull Logging logging = new Logging(); + /** Graphql integration properties. */ + private @NotNull Graphql graphql = new Graphql(); + public boolean isUseGitCommitIdAsRelease() { return useGitCommitIdAsRelease; } @@ -74,6 +78,14 @@ public void setLogging(@NotNull Logging logging) { this.logging = logging; } + public @NotNull Graphql getGraphql() { + return graphql; + } + + public void setGraphql(@NotNull Graphql graphql) { + this.graphql = graphql; + } + @Open public static class Logging { /** Enable/Disable logging auto-configuration. */ @@ -121,4 +133,20 @@ public void setLoggers(final @NotNull List loggers) { this.loggers = loggers; } } + + @Open + public static class Graphql { + + /** List of error types the Sentry Graphql integration should ignore. */ + private @NotNull List ignoredErrorTypes = new ArrayList<>(); + + @NotNull + public List getIgnoredErrorTypes() { + return ignoredErrorTypes; + } + + public void setIgnoredErrorTypes(final @NotNull List ignoredErrorTypes) { + this.ignoredErrorTypes = ignoredErrorTypes; + } + } } diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration.java new file mode 100644 index 00000000000..5c92d4bd6a8 --- /dev/null +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration.java @@ -0,0 +1,65 @@ +package io.sentry.spring.boot.graphql; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.graphql.SentryInstrumentation; +import io.sentry.spring.boot.SentryProperties; +import io.sentry.spring.graphql.SentryDataFetcherExceptionResolverAdapter; +import io.sentry.spring.graphql.SentryGraphqlBeanPostProcessor; +import io.sentry.spring.graphql.SentrySpringSubscriptionHandler; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +@Configuration(proxyBeanMethods = false) +@Open +public class SentryGraphqlAutoConfiguration { + + @Bean + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + public GraphQlSourceBuilderCustomizer sourceBuilderCustomizerWebmvc( + final @NotNull SentryProperties sentryProperties) { + SentryIntegrationPackageStorage.getInstance().addIntegration("Spring5GrahQLWebMVC"); + return sourceBuilderCustomizer(sentryProperties, false); + } + + @Bean + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + public GraphQlSourceBuilderCustomizer sourceBuilderCustomizerWebflux( + final @NotNull SentryProperties sentryProperties) { + SentryIntegrationPackageStorage.getInstance().addIntegration("Spring5GrahQLWebFlux"); + return sourceBuilderCustomizer(sentryProperties, true); + } + + /** + * We're not setting defaultDataFetcherExceptionHandler here on purpose and instead use the + * resolver adapter below. This way Springs handler can still forward to other resolver adapters. + */ + private GraphQlSourceBuilderCustomizer sourceBuilderCustomizer( + final @NotNull SentryProperties sentryProperties, final boolean captureRequestBody) { + return (builder) -> + builder.configureGraphQl( + graphQlBuilder -> + graphQlBuilder.instrumentation( + new SentryInstrumentation( + null, + new SentrySpringSubscriptionHandler(), + captureRequestBody, + sentryProperties.getGraphql().getIgnoredErrorTypes()))); + } + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentryDataFetcherExceptionResolverAdapter exceptionResolverAdapter() { + return new SentryDataFetcherExceptionResolverAdapter(); + } + + @Bean + public SentryGraphqlBeanPostProcessor graphqlBeanPostProcessor() { + return new SentryGraphqlBeanPostProcessor(); + } +} From b00553c6e85389172b54f3e708c4071d14fd77d6 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 10 Oct 2023 14:05:46 +0200 Subject: [PATCH 2/2] code review changes --- CHANGELOG.md | 3 +++ .../main/java/io/sentry/graphql/SentryInstrumentation.java | 5 +++-- .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64bb70ad353..cf556834214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Features - Add `CheckInUtils.withCheckIn` which abstracts away some of the manual check-ins complexity ([#2959](https://github.com/getsentry/sentry-java/pull/2959)) +- Allow filtering GraphQL errors ([#2967](https://github.com/getsentry/sentry-java/pull/2967)) + - This list can be set directly when calling the constructor of `SentryInstrumentation` + - For Spring Boot it can also be set in `application.properties` as `sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR` ## 6.30.0 diff --git a/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java b/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java index 8a1a9829e27..bd53bb57f21 100644 --- a/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java +++ b/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java @@ -118,6 +118,7 @@ public SentryInstrumentation( * this integration for query and mutation operations. This can be used to prevent unnecessary * work by not adding the request body when another integration will add it anyways, as is the * case with our spring integration for WebMVC. + * @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry */ public SentryInstrumentation( final @Nullable BeforeSpanCallback beforeSpan, @@ -197,8 +198,6 @@ public CompletableFuture instrumentExecutionResult( final @NotNull List errors = result.getErrors(); if (errors != null) { for (GraphQLError error : errors) { - // not capturing INTERNAL_ERRORS as they should be reported via graphQlContext - // above String errorType = getErrorType(error); if (!isIgnored(errorType)) { exceptionReporter.captureThrowable( @@ -225,6 +224,8 @@ private boolean isIgnored(final @Nullable String errorType) { return false; } + // not capturing INTERNAL_ERRORS as they should be reported via graphQlContext above + // also not capturing error types explicitly ignored by users return ERROR_TYPES_HANDLED_BY_DATA_FETCHERS.contains(errorType) || ignoredErrorTypes.contains(errorType); } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties index d06f7e0878b..d677399f7e6 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties @@ -12,6 +12,7 @@ sentry.traces-sample-rate=1.0 sentry.enable-tracing=true sentry.ignored-checkins=ignored_monitor_slug_1,ignored_monitor_slug_2 sentry.debug=true +sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR in-app-includes="io.sentry.samples" # Uncomment and set to true to enable aot compatibility diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index 31989d2dfea..75461046dbb 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -12,6 +12,7 @@ sentry.traces-sample-rate=1.0 sentry.enable-tracing=true sentry.ignored-checkins=ignored_monitor_slug_1,ignored_monitor_slug_2 sentry.debug=true +sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR in-app-includes="io.sentry.samples" # Database configuration