Skip to content

Commit 7e47940

Browse files
authored
Allow filtering graphql errors (getsentry#2967)
1 parent 86f0de8 commit 7e47940

15 files changed

Lines changed: 291 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
- Add `@SentryCaptureExceptionParameter` annotation which captures exceptions passed into an annotated method ([#2764](https://github.com/getsentry/sentry-java/pull/2764))
99
- This can be used to replace `Sentry.captureException` calls in `@ExceptionHandler` of a `@ControllerAdvice`
1010
- Add `ServerWebExchange` to `Hint` for WebFlux as `WEBFLUX_EXCEPTION_HANDLER_EXCHANGE` ([#2977](https://github.com/getsentry/sentry-java/pull/2977))
11+
- Allow filtering GraphQL errors ([#2967](https://github.com/getsentry/sentry-java/pull/2967))
12+
- This list can be set directly when calling the constructor of `SentryInstrumentation`
13+
- For Spring Boot it can also be set in `application.properties` as `sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR`
1114

1215
### Dependencies
1316

sentry-graphql/api/sentry-graphql.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ public final class io/sentry/graphql/SentryInstrumentation : graphql/execution/i
5353
public fun <init> (Lio/sentry/IHub;)V
5454
public fun <init> (Lio/sentry/IHub;Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;)V
5555
public fun <init> (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;)V
56-
public fun <init> (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;)V
56+
public fun <init> (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V
5757
public fun <init> (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
58+
public fun <init> (Lio/sentry/graphql/SentryInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V
5859
public fun <init> (Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
5960
public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;)Lgraphql/execution/instrumentation/InstrumentationContext;
6061
public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;)Lgraphql/execution/instrumentation/InstrumentationContext;

sentry-graphql/src/main/java/io/sentry/graphql/SentryInstrumentation.java

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.sentry.SentryIntegrationPackageStorage;
2727
import io.sentry.SpanStatus;
2828
import io.sentry.util.StringUtils;
29+
import java.util.ArrayList;
2930
import java.util.Arrays;
3031
import java.util.List;
3132
import java.util.Locale;
@@ -52,6 +53,8 @@ public final class SentryInstrumentation extends SimpleInstrumentation {
5253

5354
private final @NotNull ExceptionReporter exceptionReporter;
5455

56+
private final @NotNull List<String> ignoredErrorTypes;
57+
5558
/**
5659
* @deprecated please use a constructor that takes a {@link SentrySubscriptionHandler} instead.
5760
*/
@@ -104,17 +107,41 @@ public SentryInstrumentation(
104107
this(
105108
beforeSpan,
106109
subscriptionHandler,
107-
new ExceptionReporter(captureRequestBodyForNonSubscriptions));
110+
new ExceptionReporter(captureRequestBodyForNonSubscriptions),
111+
new ArrayList<>());
112+
}
113+
114+
/**
115+
* @param beforeSpan callback when a span is created
116+
* @param subscriptionHandler can report subscription errors
117+
* @param captureRequestBodyForNonSubscriptions false if request bodies should not be captured by
118+
* this integration for query and mutation operations. This can be used to prevent unnecessary
119+
* work by not adding the request body when another integration will add it anyways, as is the
120+
* case with our spring integration for WebMVC.
121+
* @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry
122+
*/
123+
public SentryInstrumentation(
124+
final @Nullable BeforeSpanCallback beforeSpan,
125+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
126+
final boolean captureRequestBodyForNonSubscriptions,
127+
final @NotNull List<String> ignoredErrorTypes) {
128+
this(
129+
beforeSpan,
130+
subscriptionHandler,
131+
new ExceptionReporter(captureRequestBodyForNonSubscriptions),
132+
ignoredErrorTypes);
108133
}
109134

110135
@TestOnly
111136
public SentryInstrumentation(
112137
final @Nullable BeforeSpanCallback beforeSpan,
113138
final @NotNull SentrySubscriptionHandler subscriptionHandler,
114-
final @NotNull ExceptionReporter exceptionReporter) {
139+
final @NotNull ExceptionReporter exceptionReporter,
140+
final @NotNull List<String> ignoredErrorTypes) {
115141
this.beforeSpan = beforeSpan;
116142
this.subscriptionHandler = subscriptionHandler;
117143
this.exceptionReporter = exceptionReporter;
144+
this.ignoredErrorTypes = ignoredErrorTypes;
118145
SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL");
119146
SentryIntegrationPackageStorage.getInstance()
120147
.addPackage("maven:io.sentry:sentry-graphql", BuildConfig.VERSION_NAME);
@@ -171,11 +198,8 @@ public CompletableFuture<ExecutionResult> instrumentExecutionResult(
171198
final @NotNull List<GraphQLError> errors = result.getErrors();
172199
if (errors != null) {
173200
for (GraphQLError error : errors) {
174-
// not capturing INTERNAL_ERRORS as they should be reported via graphQlContext
175-
// above
176201
String errorType = getErrorType(error);
177-
if (errorType == null
178-
|| !ERROR_TYPES_HANDLED_BY_DATA_FETCHERS.contains(errorType)) {
202+
if (!isIgnored(errorType)) {
179203
exceptionReporter.captureThrowable(
180204
new RuntimeException(error.getMessage()),
181205
new ExceptionReporter.ExceptionDetails(
@@ -195,6 +219,17 @@ public CompletableFuture<ExecutionResult> instrumentExecutionResult(
195219
});
196220
}
197221

222+
private boolean isIgnored(final @Nullable String errorType) {
223+
if (errorType == null) {
224+
return false;
225+
}
226+
227+
// not capturing INTERNAL_ERRORS as they should be reported via graphQlContext above
228+
// also not capturing error types explicitly ignored by users
229+
return ERROR_TYPES_HANDLED_BY_DATA_FETCHERS.contains(errorType)
230+
|| ignoredErrorTypes.contains(errorType);
231+
}
232+
198233
private @Nullable String getErrorType(final @Nullable GraphQLError error) {
199234
if (error == null) {
200235
return null;

sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationAnotherTest.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.graphql
22

3+
import graphql.ErrorClassification
34
import graphql.ErrorType
45
import graphql.ExecutionInput
56
import graphql.ExecutionResultImpl
@@ -68,7 +69,7 @@ class SentryInstrumentationAnotherTest {
6869
val query = """query greeting(name: "somename")"""
6970
val variables = mapOf("variableA" to "value a")
7071

71-
fun getSut(isTransactionActive: Boolean = true, operation: OperationDefinition.Operation = OperationDefinition.Operation.QUERY, graphQLContextParam: Map<Any?, Any?>? = null, addTransactionToTracingState: Boolean = true): SentryInstrumentation {
72+
fun getSut(isTransactionActive: Boolean = true, operation: OperationDefinition.Operation = OperationDefinition.Operation.QUERY, graphQLContextParam: Map<Any?, Any?>? = null, addTransactionToTracingState: Boolean = true, ignoredErrors: List<String> = emptyList()): SentryInstrumentation {
7273
whenever(hub.options).thenReturn(SentryOptions())
7374
activeSpan = SentryTracer(TransactionContext("name", "op"), hub)
7475

@@ -86,7 +87,7 @@ class SentryInstrumentationAnotherTest {
8687
exceptionReporter = mock<ExceptionReporter>()
8788
subscriptionHandler = mock<SentrySubscriptionHandler>()
8889
whenever(subscriptionHandler.onSubscriptionResult(any(), any(), any(), any())).thenReturn("result modified by subscription handler")
89-
val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter)
90+
val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter, ignoredErrors)
9091
dataFetcher = mock<DataFetcher<Any?>>()
9192
whenever(dataFetcher.get(any())).thenReturn("raw result")
9293
graphQLContext = GraphQLContext.newContext()
@@ -325,6 +326,19 @@ class SentryInstrumentationAnotherTest {
325326
assertSame(executionResult, result)
326327
}
327328

329+
@Test
330+
fun `does not invoke exceptionReporter for ignored errors`() {
331+
val instrumentation = fixture.getSut(ignoredErrors = listOf("SOME_ERROR"))
332+
val executionResult = ExecutionResultImpl.newExecutionResult()
333+
.data("raw result")
334+
.addError(GraphqlErrorException.newErrorException().message("exception message").errorClassification(SomeErrorClassification.SOME_ERROR).build())
335+
.build()
336+
val resultFuture = instrumentation.instrumentExecutionResult(executionResult, fixture.instrumentationExecutionParameters)
337+
verify(fixture.exceptionReporter, never()).captureThrowable(any(), any(), any())
338+
val result = resultFuture.get()
339+
assertSame(executionResult, result)
340+
}
341+
328342
@Test
329343
fun `never invokes exceptionReporter if no errors`() {
330344
val instrumentation = fixture.getSut()
@@ -343,4 +357,8 @@ class SentryInstrumentationAnotherTest {
343357
}
344358

345359
data class Show(val id: Int)
360+
361+
enum class SomeErrorClassification : ErrorClassification {
362+
SOME_ERROR;
363+
}
346364
}

sentry-graphql/src/test/kotlin/io/sentry/graphql/SentryInstrumentationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class SentryInstrumentationTest {
170170
val subscriptionHandler = mock<SentrySubscriptionHandler>()
171171
whenever(subscriptionHandler.onSubscriptionResult(any(), any(), any(), any())).thenReturn("result modified by subscription handler")
172172
val operation = OperationDefinition.Operation.SUBSCRIPTION
173-
val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter)
173+
val instrumentation = SentryInstrumentation(null, subscriptionHandler, exceptionReporter, emptyList())
174174
val dataFetcher = mock<DataFetcher<Any?>>()
175175
whenever(dataFetcher.get(any())).thenReturn("raw result")
176176
val graphQLContext = GraphQLContext.newContext().build()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ sentry.traces-sample-rate=1.0
1212
sentry.enable-tracing=true
1313
sentry.ignored-checkins=ignored_monitor_slug_1,ignored_monitor_slug_2
1414
sentry.debug=true
15+
sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR
1516
in-app-includes="io.sentry.samples"
1617

1718
# Uncomment and set to true to enable aot compatibility

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ sentry.traces-sample-rate=1.0
1212
sentry.enable-tracing=true
1313
sentry.ignored-checkins=ignored_monitor_slug_1,ignored_monitor_slug_2
1414
sentry.debug=true
15+
sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR
1516
in-app-includes="io.sentry.samples"
1617

1718
# Database configuration

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,27 @@ public class io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfiguratio
2121
public class io/sentry/spring/boot/jakarta/SentryProperties : io/sentry/SentryOptions {
2222
public fun <init> ()V
2323
public fun getExceptionResolverOrder ()I
24+
public fun getGraphql ()Lio/sentry/spring/boot/jakarta/SentryProperties$Graphql;
2425
public fun getLogging ()Lio/sentry/spring/boot/jakarta/SentryProperties$Logging;
2526
public fun getReactive ()Lio/sentry/spring/boot/jakarta/SentryProperties$Reactive;
2627
public fun getUserFilterOrder ()Ljava/lang/Integer;
2728
public fun isEnableAotCompatibility ()Z
2829
public fun isUseGitCommitIdAsRelease ()Z
2930
public fun setEnableAotCompatibility (Z)V
3031
public fun setExceptionResolverOrder (I)V
32+
public fun setGraphql (Lio/sentry/spring/boot/jakarta/SentryProperties$Graphql;)V
3133
public fun setLogging (Lio/sentry/spring/boot/jakarta/SentryProperties$Logging;)V
3234
public fun setReactive (Lio/sentry/spring/boot/jakarta/SentryProperties$Reactive;)V
3335
public fun setUseGitCommitIdAsRelease (Z)V
3436
public fun setUserFilterOrder (Ljava/lang/Integer;)V
3537
}
3638

39+
public class io/sentry/spring/boot/jakarta/SentryProperties$Graphql {
40+
public fun <init> ()V
41+
public fun getIgnoredErrorTypes ()Ljava/util/List;
42+
public fun setIgnoredErrorTypes (Ljava/util/List;)V
43+
}
44+
3745
public class io/sentry/spring/boot/jakarta/SentryProperties$Logging {
3846
public fun <init> ()V
3947
public fun getLoggers ()Ljava/util/List;
@@ -57,3 +65,11 @@ public class io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration {
5765
public fun sentryWebExceptionHandler (Lio/sentry/IHub;)Lio/sentry/spring/jakarta/webflux/SentryWebExceptionHandler;
5866
}
5967

68+
public class io/sentry/spring/boot/jakarta/graphql/SentryGraphqlAutoConfiguration {
69+
public fun <init> ()V
70+
public fun exceptionResolverAdapter ()Lio/sentry/spring/jakarta/graphql/SentryDataFetcherExceptionResolverAdapter;
71+
public fun graphqlBeanPostProcessor ()Lio/sentry/spring/jakarta/graphql/SentryGraphqlBeanPostProcessor;
72+
public fun sourceBuilderCustomizerWebflux (Lio/sentry/spring/boot/jakarta/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer;
73+
public fun sourceBuilderCustomizerWebmvc (Lio/sentry/spring/boot/jakarta/SentryProperties;)Lorg/springframework/boot/autoconfigure/graphql/GraphQlSourceBuilderCustomizer;
74+
}
75+

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor;
1515
import io.sentry.protocol.SdkVersion;
1616
import io.sentry.quartz.SentryJobListener;
17+
import io.sentry.spring.boot.jakarta.graphql.SentryGraphqlAutoConfiguration;
1718
import io.sentry.spring.jakarta.ContextTagsEventProcessor;
1819
import io.sentry.spring.jakarta.SentryExceptionResolver;
1920
import io.sentry.spring.jakarta.SentryRequestResolver;
@@ -27,7 +28,6 @@
2728
import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration;
2829
import io.sentry.spring.jakarta.exception.SentryCaptureExceptionParameterPointcutConfiguration;
2930
import io.sentry.spring.jakarta.exception.SentryExceptionParameterAdviceConfiguration;
30-
import io.sentry.spring.jakarta.graphql.SentryGraphqlConfiguration;
3131
import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration;
3232
import io.sentry.spring.jakarta.tracing.SentrySpanPointcutConfiguration;
3333
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
@@ -166,7 +166,7 @@ static class OpenTelemetryLinkErrorEventProcessorConfiguration {
166166
}
167167

168168
@Configuration(proxyBeanMethods = false)
169-
@Import(SentryGraphqlConfiguration.class)
169+
@Import(SentryGraphqlAutoConfiguration.class)
170170
@Open
171171
@ConditionalOnClass({
172172
SentryGraphqlExceptionHandler.class,

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.jakewharton.nopen.annotation.Open;
44
import io.sentry.SentryOptions;
5+
import java.util.ArrayList;
56
import java.util.Arrays;
67
import java.util.List;
78
import org.jetbrains.annotations.NotNull;
@@ -40,6 +41,9 @@ public class SentryProperties extends SentryOptions {
4041
*/
4142
private boolean enableAotCompatibility = false;
4243

44+
/** Graphql integration properties. */
45+
private @NotNull Graphql graphql = new Graphql();
46+
4347
public boolean isUseGitCommitIdAsRelease() {
4448
return useGitCommitIdAsRelease;
4549
}
@@ -100,6 +104,14 @@ public void setEnableAotCompatibility(boolean enableAotCompatibility) {
100104
this.enableAotCompatibility = enableAotCompatibility;
101105
}
102106

107+
public @NotNull Graphql getGraphql() {
108+
return graphql;
109+
}
110+
111+
public void setGraphql(@NotNull Graphql graphql) {
112+
this.graphql = graphql;
113+
}
114+
103115
@Open
104116
public static class Logging {
105117
/** Enable/Disable logging auto-configuration. */
@@ -163,4 +175,20 @@ public void setThreadLocalAccessorEnabled(boolean threadLocalAccessorEnabled) {
163175
this.threadLocalAccessorEnabled = threadLocalAccessorEnabled;
164176
}
165177
}
178+
179+
@Open
180+
public static class Graphql {
181+
182+
/** List of error types the Sentry Graphql integration should ignore. */
183+
private @NotNull List<String> ignoredErrorTypes = new ArrayList<>();
184+
185+
@NotNull
186+
public List<String> getIgnoredErrorTypes() {
187+
return ignoredErrorTypes;
188+
}
189+
190+
public void setIgnoredErrorTypes(final @NotNull List<String> ignoredErrorTypes) {
191+
this.ignoredErrorTypes = ignoredErrorTypes;
192+
}
193+
}
166194
}

0 commit comments

Comments
 (0)