diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd49952d1d..9bf59e7c5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- Add opt-in support for matching GraphQL ignored error types against classification specifications via `sentry.graphql.enable-specification-error-type-filtering` ([#6092](https://github.com/getsentry/sentry-java/pull/6092)) - Session Replay: Fix `SentryOkHttpInterceptor` hanging forever on responses whose body has no known length, such as Server-Sent Events or a gzipped response, when network details are enabled for the URL ([#6231](https://github.com/getsentry/sentry-java/pull/6231)) - Stop marking an OkHttp call span as failed when a connection attempt to one of the host's addresses fails but OkHttp connects on another ([#6472](https://github.com/getsentry/sentry-java/pull/6472)) diff --git a/sentry-graphql-22/api/sentry-graphql-22.api b/sentry-graphql-22/api/sentry-graphql-22.api index 2bee0cf765d..b1e5b00adf2 100644 --- a/sentry-graphql-22/api/sentry-graphql-22.api +++ b/sentry-graphql-22/api/sentry-graphql-22.api @@ -7,8 +7,10 @@ public final class io/sentry/graphql22/SentryInstrumentation : graphql/execution public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String; public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String; public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Z)V public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Z)V public fun (Lio/sentry/graphql/SentrySubscriptionHandler;Z)V public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;Lgraphql/execution/instrumentation/InstrumentationState;)Lgraphql/execution/instrumentation/InstrumentationContext; public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;Lgraphql/execution/instrumentation/InstrumentationState;)Lgraphql/execution/instrumentation/InstrumentationContext; diff --git a/sentry-graphql-22/src/main/java/io/sentry/graphql22/SentryInstrumentation.java b/sentry-graphql-22/src/main/java/io/sentry/graphql22/SentryInstrumentation.java index 264c0a99da2..d4833cafee3 100644 --- a/sentry-graphql-22/src/main/java/io/sentry/graphql22/SentryInstrumentation.java +++ b/sentry-graphql-22/src/main/java/io/sentry/graphql22/SentryInstrumentation.java @@ -77,11 +77,35 @@ public SentryInstrumentation( final @NotNull SentrySubscriptionHandler subscriptionHandler, final boolean captureRequestBodyForNonSubscriptions, final @NotNull List ignoredErrorTypes) { + this( + beforeSpan, + subscriptionHandler, + captureRequestBodyForNonSubscriptions, + ignoredErrorTypes, + false); + } + + /** + * @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 + * @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry + * @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched + * against {@link graphql.ErrorClassification#toSpecification(graphql.GraphQLError)} + */ + public SentryInstrumentation( + final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final boolean captureRequestBodyForNonSubscriptions, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering) { this( beforeSpan, subscriptionHandler, new ExceptionReporter(captureRequestBodyForNonSubscriptions), - ignoredErrorTypes); + ignoredErrorTypes, + enableSpecificationErrorTypeFiltering); } @TestOnly @@ -90,9 +114,24 @@ public SentryInstrumentation( final @NotNull SentrySubscriptionHandler subscriptionHandler, final @NotNull ExceptionReporter exceptionReporter, final @NotNull List ignoredErrorTypes) { + this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false); + } + + @TestOnly + public SentryInstrumentation( + final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final @NotNull ExceptionReporter exceptionReporter, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering) { this.instrumentation = new SentryGraphqlInstrumentation( - beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, TRACE_ORIGIN); + beforeSpan, + subscriptionHandler, + exceptionReporter, + ignoredErrorTypes, + enableSpecificationErrorTypeFiltering, + TRACE_ORIGIN); SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL-v22"); } diff --git a/sentry-graphql-core/api/sentry-graphql-core.api b/sentry-graphql-core/api/sentry-graphql-core.api index 7b63e2270d4..7ab6b58db2c 100644 --- a/sentry-graphql-core/api/sentry-graphql-core.api +++ b/sentry-graphql-core/api/sentry-graphql-core.api @@ -46,7 +46,9 @@ public final class io/sentry/graphql/SentryGraphqlInstrumentation { public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String; public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String; public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Ljava/lang/String;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;ZLjava/lang/String;)V public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Ljava/lang/String;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;ZLjava/lang/String;)V public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;)V public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;Lio/sentry/graphql/SentryGraphqlInstrumentation$TracingState;)V public fun createState ()Lgraphql/execution/instrumentation/InstrumentationState; diff --git a/sentry-graphql-core/src/main/java/io/sentry/graphql/SentryGraphqlInstrumentation.java b/sentry-graphql-core/src/main/java/io/sentry/graphql/SentryGraphqlInstrumentation.java index b1f2892500a..cb1757e30d4 100644 --- a/sentry-graphql-core/src/main/java/io/sentry/graphql/SentryGraphqlInstrumentation.java +++ b/sentry-graphql-core/src/main/java/io/sentry/graphql/SentryGraphqlInstrumentation.java @@ -55,6 +55,7 @@ public final class SentryGraphqlInstrumentation { private final @NotNull SentrySubscriptionHandler subscriptionHandler; private final @NotNull ExceptionReporter exceptionReporter; private final @NotNull List ignoredErrorTypes; + private final boolean enableSpecificationErrorTypeFiltering; private final @NotNull String traceOrigin; /** @@ -72,11 +73,38 @@ public SentryGraphqlInstrumentation( final boolean captureRequestBodyForNonSubscriptions, final @NotNull List ignoredErrorTypes, final @NotNull String traceOrigin) { + this( + beforeSpan, + subscriptionHandler, + captureRequestBodyForNonSubscriptions, + ignoredErrorTypes, + false, + traceOrigin); + } + + /** + * @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 + * @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry + * @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched + * against {@link ErrorClassification#toSpecification(GraphQLError)} + * @param traceOrigin origin used for spans created by this integration + */ + public SentryGraphqlInstrumentation( + final @Nullable BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final boolean captureRequestBodyForNonSubscriptions, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering, + final @NotNull String traceOrigin) { this( beforeSpan, subscriptionHandler, new ExceptionReporter(captureRequestBodyForNonSubscriptions), ignoredErrorTypes, + enableSpecificationErrorTypeFiltering, traceOrigin); } @@ -87,10 +115,22 @@ public SentryGraphqlInstrumentation( final @NotNull ExceptionReporter exceptionReporter, final @NotNull List ignoredErrorTypes, final @NotNull String traceOrigin) { + this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false, traceOrigin); + } + + @TestOnly + public SentryGraphqlInstrumentation( + final @Nullable BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final @NotNull ExceptionReporter exceptionReporter, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering, + final @NotNull String traceOrigin) { this.beforeSpan = beforeSpan; this.subscriptionHandler = subscriptionHandler; this.exceptionReporter = exceptionReporter; this.ignoredErrorTypes = ignoredErrorTypes; + this.enableSpecificationErrorTypeFiltering = enableSpecificationErrorTypeFiltering; this.traceOrigin = traceOrigin; } @@ -127,8 +167,7 @@ public void instrumentExecutionResultComplete( final @NotNull List errors = result.getErrors(); if (errors != null) { for (GraphQLError error : errors) { - String errorType = getErrorType(error); - if (!isIgnored(errorType)) { + if (!isIgnored(error)) { exceptionReporter.captureThrowable( new RuntimeException(error.getMessage()), new ExceptionReporter.ExceptionDetails( @@ -158,19 +197,38 @@ private boolean isIgnored(final @Nullable String errorType) { || ignoredErrorTypes.contains(errorType); } - private @Nullable String getErrorType(final @Nullable GraphQLError error) { + private boolean isIgnored(final @Nullable GraphQLError error) { if (error == null) { - return null; + return false; } final @Nullable ErrorClassification errorType = error.getErrorType(); if (errorType != null) { - return errorType.toString(); + if (isIgnored(errorType.toString())) { + return true; + } + if (!enableSpecificationErrorTypeFiltering) { + return false; + } + try { + final @Nullable Object specification = errorType.toSpecification(error); + if (specification instanceof String && isIgnored((String) specification)) { + return true; + } + if (specification instanceof Map) { + final @Nullable Object specificationType = ((Map) specification).get("type"); + return specificationType instanceof String && isIgnored((String) specificationType); + } + } catch (Throwable throwable) { + ExceptionUtils.rethrowIfFatal(throwable); + // Fall back to capturing the error if a custom classification cannot be serialized. + } + return false; } final @Nullable Map extensions = error.getExtensions(); if (extensions != null) { - return StringUtils.toString(extensions.get("errorType")); + return isIgnored(StringUtils.toString(extensions.get("errorType"))); } - return null; + return false; } public void beginExecuteOperation( diff --git a/sentry-graphql-core/src/test/kotlin/io/sentry/graphql/SentryGraphqlInstrumentationTest.kt b/sentry-graphql-core/src/test/kotlin/io/sentry/graphql/SentryGraphqlInstrumentationTest.kt new file mode 100644 index 00000000000..09843710bea --- /dev/null +++ b/sentry-graphql-core/src/test/kotlin/io/sentry/graphql/SentryGraphqlInstrumentationTest.kt @@ -0,0 +1,198 @@ +package io.sentry.graphql + +import graphql.ErrorClassification +import graphql.ExecutionResultImpl +import graphql.GraphQLContext +import graphql.GraphQLError +import graphql.GraphqlErrorException +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters +import io.sentry.IScopes +import kotlin.test.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class SentryGraphqlInstrumentationTest { + private val scopes = mock() + private val exceptionReporter = mock() + private val parameters = mock() + + init { + whenever(parameters.graphQLContext) + .thenReturn( + GraphQLContext.newContext() + .of(SentryGraphqlInstrumentation.SENTRY_SCOPES_CONTEXT_KEY, scopes) + .build() + ) + } + + @Test + fun `ignores error by classification string`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", mapOf("type" to "OTHER_ERROR"))), + ignoredErrorTypes = listOf("SOME_ERROR"), + ) + + verify(exceptionReporter, never()).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures error by classification specification type when filtering is disabled`() { + captureError( + error( + CustomErrorClassification( + "graphql.validation.interpolation.ResourceBundleMessageInterpolator\$ValidationErrorType@1", + mapOf("type" to "ExtendedValidationError"), + ) + ), + ignoredErrorTypes = listOf("ExtendedValidationError"), + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + @Test + fun `does not inspect classification specification when filtering is disabled`() { + val classification = mock() + whenever(classification.toString()).thenReturn("SOME_ERROR") + + captureError( + error(classification), + ignoredErrorTypes = listOf("SPECIFICATION_ERROR"), + ) + + verify(classification, never()).toSpecification(any()) + } + + @Test + fun `ignores error by classification specification type when filtering is enabled`() { + captureError( + error( + CustomErrorClassification( + "graphql.validation.interpolation.ResourceBundleMessageInterpolator\$ValidationErrorType@1", + mapOf("type" to "ExtendedValidationError"), + ) + ), + ignoredErrorTypes = listOf("ExtendedValidationError"), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter, never()).captureThrowable(any(), any(), any()) + } + + @Test + fun `ignores error by string classification specification`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", "SPECIFICATION_ERROR")), + ignoredErrorTypes = listOf("SPECIFICATION_ERROR"), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter, never()).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures error when classification specification has no type`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", mapOf("constraint" to "@Size"))), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures error when classification specification is null`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", null)), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures error when classification specification type is unexpected`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", mapOf("type" to 42))), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures error when classification specification throws`() { + captureError( + error(ThrowingErrorClassification), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + @Test + fun `ignores error by extensions when classification is null`() { + val error = mock() + whenever(error.errorType).thenReturn(null) + whenever(error.extensions).thenReturn(mapOf("errorType" to "EXTENSION_ERROR")) + + captureError(error, ignoredErrorTypes = listOf("EXTENSION_ERROR")) + + verify(exceptionReporter, never()).captureThrowable(any(), any(), any()) + } + + @Test + fun `captures non-ignored error`() { + captureError( + error(CustomErrorClassification("SOME_ERROR", mapOf("type" to "SPECIFICATION_ERROR"))), + ignoredErrorTypes = listOf("OTHER_ERROR"), + enableSpecificationErrorTypeFiltering = true, + ) + + verify(exceptionReporter).captureThrowable(any(), any(), any()) + } + + private fun captureError( + error: GraphQLError, + ignoredErrorTypes: List = emptyList(), + enableSpecificationErrorTypeFiltering: Boolean = false, + ) { + val instrumentation = + SentryGraphqlInstrumentation( + null, + NoOpSubscriptionHandler.getInstance(), + exceptionReporter, + ignoredErrorTypes, + enableSpecificationErrorTypeFiltering, + "manual.test", + ) + val result = ExecutionResultImpl.newExecutionResult().addError(error).build() + + instrumentation.instrumentExecutionResultComplete(parameters, result, null) + } + + private fun error(errorClassification: ErrorClassification): GraphQLError = + GraphqlErrorException.newErrorException() + .message("exception message") + .errorClassification(errorClassification) + .build() + + private class CustomErrorClassification( + private val stringValue: String, + private val specification: Any?, + ) : ErrorClassification { + override fun toSpecification(error: GraphQLError): Any? = specification + + override fun toString(): String = stringValue + } + + private object ThrowingErrorClassification : ErrorClassification { + override fun toSpecification(error: GraphQLError): Any = + throw AssertionError("failed to create specification") + + override fun toString(): String = "SOME_ERROR" + } +} diff --git a/sentry-graphql/api/sentry-graphql.api b/sentry-graphql/api/sentry-graphql.api index 2e5f81ae760..4f58e5ee912 100644 --- a/sentry-graphql/api/sentry-graphql.api +++ b/sentry-graphql/api/sentry-graphql.api @@ -7,8 +7,10 @@ public final class io/sentry/graphql/SentryInstrumentation : graphql/execution/i public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String; public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String; public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Z)V public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V + public fun (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Z)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 b8d35697f1f..0d0736f8b70 100644 --- a/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java +++ b/sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java @@ -74,11 +74,35 @@ public SentryInstrumentation( final @NotNull SentrySubscriptionHandler subscriptionHandler, final boolean captureRequestBodyForNonSubscriptions, final @NotNull List ignoredErrorTypes) { + this( + beforeSpan, + subscriptionHandler, + captureRequestBodyForNonSubscriptions, + ignoredErrorTypes, + false); + } + + /** + * @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 + * @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry + * @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched + * against {@link graphql.ErrorClassification#toSpecification(graphql.GraphQLError)} + */ + public SentryInstrumentation( + final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final boolean captureRequestBodyForNonSubscriptions, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering) { this( beforeSpan, subscriptionHandler, new ExceptionReporter(captureRequestBodyForNonSubscriptions), - ignoredErrorTypes); + ignoredErrorTypes, + enableSpecificationErrorTypeFiltering); } @TestOnly @@ -87,9 +111,24 @@ public SentryInstrumentation( final @NotNull SentrySubscriptionHandler subscriptionHandler, final @NotNull ExceptionReporter exceptionReporter, final @NotNull List ignoredErrorTypes) { + this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false); + } + + @TestOnly + public SentryInstrumentation( + final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan, + final @NotNull SentrySubscriptionHandler subscriptionHandler, + final @NotNull ExceptionReporter exceptionReporter, + final @NotNull List ignoredErrorTypes, + final boolean enableSpecificationErrorTypeFiltering) { this.instrumentation = new SentryGraphqlInstrumentation( - beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, TRACE_ORIGIN); + beforeSpan, + subscriptionHandler, + exceptionReporter, + ignoredErrorTypes, + enableSpecificationErrorTypeFiltering, + TRACE_ORIGIN); SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL"); } diff --git a/sentry-spring-boot-4/api/sentry-spring-boot-4.api b/sentry-spring-boot-4/api/sentry-spring-boot-4.api index 080bb6d6f6a..432ed580bd5 100644 --- a/sentry-spring-boot-4/api/sentry-spring-boot-4.api +++ b/sentry-spring-boot-4/api/sentry-spring-boot-4.api @@ -74,6 +74,8 @@ public class io/sentry/spring/boot4/SentryProperties$Clients { public class io/sentry/spring/boot4/SentryProperties$Graphql { public fun ()V public fun getIgnoredErrorTypes ()Ljava/util/List; + public fun isEnableSpecificationErrorTypeFiltering ()Z + public fun setEnableSpecificationErrorTypeFiltering (Z)V public fun setIgnoredErrorTypes (Ljava/util/List;)V } diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java index b99450644fc..3a30d465737 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java @@ -242,6 +242,9 @@ public static class Graphql { /** List of error types the Sentry Graphql integration should ignore. */ private @NotNull List ignoredErrorTypes = new ArrayList<>(); + /** Whether ignored error types should be matched against classification specifications. */ + private boolean enableSpecificationErrorTypeFiltering = false; + @NotNull public List getIgnoredErrorTypes() { return ignoredErrorTypes; @@ -250,6 +253,15 @@ public List getIgnoredErrorTypes() { public void setIgnoredErrorTypes(final @NotNull List ignoredErrorTypes) { this.ignoredErrorTypes = ignoredErrorTypes; } + + public boolean isEnableSpecificationErrorTypeFiltering() { + return enableSpecificationErrorTypeFiltering; + } + + public void setEnableSpecificationErrorTypeFiltering( + final boolean enableSpecificationErrorTypeFiltering) { + this.enableSpecificationErrorTypeFiltering = enableSpecificationErrorTypeFiltering; + } } @Open diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphql22AutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphql22AutoConfiguration.java index e83b6241847..f1a9c74ebb0 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphql22AutoConfiguration.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphql22AutoConfiguration.java @@ -56,7 +56,8 @@ private SentryInstrumentation createInstrumentation( beforeSpanCallback.getIfAvailable(), new SentrySpringSubscriptionHandler(), captureRequestBody, - sentryProperties.getGraphql().getIgnoredErrorTypes()); + sentryProperties.getGraphql().getIgnoredErrorTypes(), + sentryProperties.getGraphql().isEnableSpecificationErrorTypeFiltering()); } @Bean diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphqlAutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphqlAutoConfiguration.java index 7bcef3c7b4d..7565b48a25c 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphqlAutoConfiguration.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/graphql/SentryGraphqlAutoConfiguration.java @@ -56,7 +56,8 @@ private SentryInstrumentation createInstrumentation( beforeSpanCallback.getIfAvailable(), new SentrySpringSubscriptionHandler(), captureRequestBody, - sentryProperties.getGraphql().getIgnoredErrorTypes()); + sentryProperties.getGraphql().getIgnoredErrorTypes(), + sentryProperties.getGraphql().isEnableSpecificationErrorTypeFiltering()); } @Bean diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt index 6cd61dae0de..334153d4e86 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt @@ -203,6 +203,33 @@ class SentryAutoConfigurationTest { } } + @Test + fun `GraphQL specification error type filtering is disabled by default`() { + dsnEnabledRunner.run { + assertThat( + it.getBean(SentryProperties::class.java).graphql.isEnableSpecificationErrorTypeFiltering + ) + .isFalse() + } + } + + @Test + fun `GraphQL specification error type filtering property is applied`() { + listOf(false, true).forEach { enabled -> + dsnEnabledRunner + .withPropertyValues("sentry.graphql.enable-specification-error-type-filtering=$enabled") + .run { + assertThat( + it + .getBean(SentryProperties::class.java) + .graphql + .isEnableSpecificationErrorTypeFiltering + ) + .isEqualTo(enabled) + } + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner 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 9b9c46158bc..da23ccf6ca3 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -74,6 +74,8 @@ public class io/sentry/spring/boot/jakarta/SentryProperties$Clients { public class io/sentry/spring/boot/jakarta/SentryProperties$Graphql { public fun ()V public fun getIgnoredErrorTypes ()Ljava/util/List; + public fun isEnableSpecificationErrorTypeFiltering ()Z + public fun setEnableSpecificationErrorTypeFiltering (Z)V public fun setIgnoredErrorTypes (Ljava/util/List;)V } 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 c7241a9466e..f3457d17f12 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 @@ -256,6 +256,9 @@ public static class Graphql { /** List of error types the Sentry Graphql integration should ignore. */ private @NotNull List ignoredErrorTypes = new ArrayList<>(); + /** Whether ignored error types should be matched against classification specifications. */ + private boolean enableSpecificationErrorTypeFiltering = false; + @NotNull public List getIgnoredErrorTypes() { return ignoredErrorTypes; @@ -264,5 +267,14 @@ public List getIgnoredErrorTypes() { public void setIgnoredErrorTypes(final @NotNull List ignoredErrorTypes) { this.ignoredErrorTypes = ignoredErrorTypes; } + + public boolean isEnableSpecificationErrorTypeFiltering() { + return enableSpecificationErrorTypeFiltering; + } + + public void setEnableSpecificationErrorTypeFiltering( + final boolean enableSpecificationErrorTypeFiltering) { + this.enableSpecificationErrorTypeFiltering = enableSpecificationErrorTypeFiltering; + } } } diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration.java index aa741892848..defbc59f5ae 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration.java @@ -56,7 +56,8 @@ private SentryInstrumentation createInstrumentation( beforeSpanCallback.getIfAvailable(), new SentrySpringSubscriptionHandler(), captureRequestBody, - sentryProperties.getGraphql().getIgnoredErrorTypes()); + sentryProperties.getGraphql().getIgnoredErrorTypes(), + sentryProperties.getGraphql().isEnableSpecificationErrorTypeFiltering()); } @Bean 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 index e5c1da2df76..7c6c14f8d3f 100644 --- 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 @@ -56,7 +56,8 @@ private SentryInstrumentation createInstrumentation( beforeSpanCallback.getIfAvailable(), new SentrySpringSubscriptionHandler(), captureRequestBody, - sentryProperties.getGraphql().getIgnoredErrorTypes()); + sentryProperties.getGraphql().getIgnoredErrorTypes(), + sentryProperties.getGraphql().isEnableSpecificationErrorTypeFiltering()); } @Bean diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index 1217bbfea40..4f602db34f4 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -206,6 +206,33 @@ class SentryAutoConfigurationTest { } } + @Test + fun `GraphQL specification error type filtering is disabled by default`() { + dsnEnabledRunner.run { + assertThat( + it.getBean(SentryProperties::class.java).graphql.isEnableSpecificationErrorTypeFiltering + ) + .isFalse() + } + } + + @Test + fun `GraphQL specification error type filtering property is applied`() { + listOf(false, true).forEach { enabled -> + dsnEnabledRunner + .withPropertyValues("sentry.graphql.enable-specification-error-type-filtering=$enabled") + .run { + assertThat( + it + .getBean(SentryProperties::class.java) + .graphql + .isEnableSpecificationErrorTypeFiltering + ) + .isEqualTo(enabled) + } + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index 4fafe9d695b..5be7b5295d4 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -59,6 +59,8 @@ public class io/sentry/spring/boot/SentryProperties$Clients { public class io/sentry/spring/boot/SentryProperties$Graphql { public fun ()V public fun getIgnoredErrorTypes ()Ljava/util/List; + public fun isEnableSpecificationErrorTypeFiltering ()Z + public fun setEnableSpecificationErrorTypeFiltering (Z)V public fun setIgnoredErrorTypes (Ljava/util/List;)V } 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 cf8c6934c50..fcca4fe7fc9 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 @@ -213,6 +213,9 @@ public static class Graphql { /** List of error types the Sentry Graphql integration should ignore. */ private @NotNull List ignoredErrorTypes = new ArrayList<>(); + /** Whether ignored error types should be matched against classification specifications. */ + private boolean enableSpecificationErrorTypeFiltering = false; + @NotNull public List getIgnoredErrorTypes() { return ignoredErrorTypes; @@ -221,5 +224,14 @@ public List getIgnoredErrorTypes() { public void setIgnoredErrorTypes(final @NotNull List ignoredErrorTypes) { this.ignoredErrorTypes = ignoredErrorTypes; } + + public boolean isEnableSpecificationErrorTypeFiltering() { + return enableSpecificationErrorTypeFiltering; + } + + public void setEnableSpecificationErrorTypeFiltering( + final boolean enableSpecificationErrorTypeFiltering) { + this.enableSpecificationErrorTypeFiltering = enableSpecificationErrorTypeFiltering; + } } } 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 index ebebe1f1499..40f6fcb2eb7 100644 --- 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 @@ -56,7 +56,8 @@ private SentryInstrumentation createInstrumentation( beforeSpanCallback.getIfAvailable(), new SentrySpringSubscriptionHandler(), captureRequestBody, - sentryProperties.getGraphql().getIgnoredErrorTypes()); + sentryProperties.getGraphql().getIgnoredErrorTypes(), + sentryProperties.getGraphql().isEnableSpecificationErrorTypeFiltering()); } @Bean diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index 20215ee145f..f4f14c6a85e 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -204,6 +204,33 @@ class SentryAutoConfigurationTest { } } + @Test + fun `GraphQL specification error type filtering is disabled by default`() { + dsnEnabledRunner.run { + assertThat( + it.getBean(SentryProperties::class.java).graphql.isEnableSpecificationErrorTypeFiltering + ) + .isFalse() + } + } + + @Test + fun `GraphQL specification error type filtering property is applied`() { + listOf(false, true).forEach { enabled -> + dsnEnabledRunner + .withPropertyValues("sentry.graphql.enable-specification-error-type-filtering=$enabled") + .run { + assertThat( + it + .getBean(SentryProperties::class.java) + .graphql + .isEnableSpecificationErrorTypeFiltering + ) + .isEqualTo(enabled) + } + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner