Skip to content

Commit 2fd2ccb

Browse files
adinauerclaude
andcommitted
fix(graphql): Make specification filtering opt-in
Add an explicit option before matching ignored GraphQL error types against classification specifications. Keep the existing behavior unchanged by default to avoid suppressing errors based on custom specification output. Expose the option through the Spring Boot GraphQL configuration. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 41a83ff commit 2fd2ccb

22 files changed

Lines changed: 319 additions & 15 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@
249249

250250
### Fixes
251251

252+
- 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))
252253
- Update `SentryTraced` so that it now honors `options.setIgnoredSpanOrigins` ([#6058](https://github.com/getsentry/sentry-java/pull/6058))
253254
- `SentryTraced` now checks for its owning transaction dynamically rather than once per app process. The latter caused `SentryTraced` spans to be dropped process-wide once the original transaction finished ([#6057](https://github.com/getsentry/sentry-java/pull/6057))
254255
- Fix typos in Spring GraphQL integration names (`GrahQL` to `GraphQL`) ([#6061](https://github.com/getsentry/sentry-java/pull/6061))

‎sentry-graphql-22/api/sentry-graphql-22.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ public final class io/sentry/graphql22/SentryInstrumentation : graphql/execution
77
public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String;
88
public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String;
99
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V
10+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Z)V
1011
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
1112
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V
13+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Z)V
1214
public fun <init> (Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
1315
public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;Lgraphql/execution/instrumentation/InstrumentationState;)Lgraphql/execution/instrumentation/InstrumentationContext;
1416
public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;Lgraphql/execution/instrumentation/InstrumentationState;)Lgraphql/execution/instrumentation/InstrumentationContext;

‎sentry-graphql-22/src/main/java/io/sentry/graphql22/SentryInstrumentation.java‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,35 @@ public SentryInstrumentation(
7777
final @NotNull SentrySubscriptionHandler subscriptionHandler,
7878
final boolean captureRequestBodyForNonSubscriptions,
7979
final @NotNull List<String> ignoredErrorTypes) {
80+
this(
81+
beforeSpan,
82+
subscriptionHandler,
83+
captureRequestBodyForNonSubscriptions,
84+
ignoredErrorTypes,
85+
false);
86+
}
87+
88+
/**
89+
* @param beforeSpan callback when a span is created
90+
* @param subscriptionHandler can report subscription errors
91+
* @param captureRequestBodyForNonSubscriptions false if request bodies should not be captured by
92+
* this integration for query and mutation operations
93+
* @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry
94+
* @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched
95+
* against {@link graphql.ErrorClassification#toSpecification(graphql.GraphQLError)}
96+
*/
97+
public SentryInstrumentation(
98+
final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan,
99+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
100+
final boolean captureRequestBodyForNonSubscriptions,
101+
final @NotNull List<String> ignoredErrorTypes,
102+
final boolean enableSpecificationErrorTypeFiltering) {
80103
this(
81104
beforeSpan,
82105
subscriptionHandler,
83106
new ExceptionReporter(captureRequestBodyForNonSubscriptions),
84-
ignoredErrorTypes);
107+
ignoredErrorTypes,
108+
enableSpecificationErrorTypeFiltering);
85109
}
86110

87111
@TestOnly
@@ -90,9 +114,24 @@ public SentryInstrumentation(
90114
final @NotNull SentrySubscriptionHandler subscriptionHandler,
91115
final @NotNull ExceptionReporter exceptionReporter,
92116
final @NotNull List<String> ignoredErrorTypes) {
117+
this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false);
118+
}
119+
120+
@TestOnly
121+
public SentryInstrumentation(
122+
final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan,
123+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
124+
final @NotNull ExceptionReporter exceptionReporter,
125+
final @NotNull List<String> ignoredErrorTypes,
126+
final boolean enableSpecificationErrorTypeFiltering) {
93127
this.instrumentation =
94128
new SentryGraphqlInstrumentation(
95-
beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, TRACE_ORIGIN);
129+
beforeSpan,
130+
subscriptionHandler,
131+
exceptionReporter,
132+
ignoredErrorTypes,
133+
enableSpecificationErrorTypeFiltering,
134+
TRACE_ORIGIN);
96135
SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL-v22");
97136
}
98137

‎sentry-graphql-core/api/sentry-graphql-core.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ public final class io/sentry/graphql/SentryGraphqlInstrumentation {
4646
public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String;
4747
public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String;
4848
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Ljava/lang/String;)V
49+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;ZLjava/lang/String;)V
4950
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Ljava/lang/String;)V
51+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;ZLjava/lang/String;)V
5052
public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;)V
5153
public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;Lio/sentry/graphql/SentryGraphqlInstrumentation$TracingState;)V
5254
public fun createState ()Lgraphql/execution/instrumentation/InstrumentationState;

‎sentry-graphql-core/src/main/java/io/sentry/graphql/SentryGraphqlInstrumentation.java‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public final class SentryGraphqlInstrumentation {
5555
private final @NotNull SentrySubscriptionHandler subscriptionHandler;
5656
private final @NotNull ExceptionReporter exceptionReporter;
5757
private final @NotNull List<String> ignoredErrorTypes;
58+
private final boolean enableSpecificationErrorTypeFiltering;
5859
private final @NotNull String traceOrigin;
5960

6061
/**
@@ -72,11 +73,38 @@ public SentryGraphqlInstrumentation(
7273
final boolean captureRequestBodyForNonSubscriptions,
7374
final @NotNull List<String> ignoredErrorTypes,
7475
final @NotNull String traceOrigin) {
76+
this(
77+
beforeSpan,
78+
subscriptionHandler,
79+
captureRequestBodyForNonSubscriptions,
80+
ignoredErrorTypes,
81+
false,
82+
traceOrigin);
83+
}
84+
85+
/**
86+
* @param beforeSpan callback when a span is created
87+
* @param subscriptionHandler can report subscription errors
88+
* @param captureRequestBodyForNonSubscriptions false if request bodies should not be captured by
89+
* this integration for query and mutation operations
90+
* @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry
91+
* @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched
92+
* against {@link ErrorClassification#toSpecification(GraphQLError)}
93+
* @param traceOrigin origin used for spans created by this integration
94+
*/
95+
public SentryGraphqlInstrumentation(
96+
final @Nullable BeforeSpanCallback beforeSpan,
97+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
98+
final boolean captureRequestBodyForNonSubscriptions,
99+
final @NotNull List<String> ignoredErrorTypes,
100+
final boolean enableSpecificationErrorTypeFiltering,
101+
final @NotNull String traceOrigin) {
75102
this(
76103
beforeSpan,
77104
subscriptionHandler,
78105
new ExceptionReporter(captureRequestBodyForNonSubscriptions),
79106
ignoredErrorTypes,
107+
enableSpecificationErrorTypeFiltering,
80108
traceOrigin);
81109
}
82110

@@ -87,10 +115,22 @@ public SentryGraphqlInstrumentation(
87115
final @NotNull ExceptionReporter exceptionReporter,
88116
final @NotNull List<String> ignoredErrorTypes,
89117
final @NotNull String traceOrigin) {
118+
this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false, traceOrigin);
119+
}
120+
121+
@TestOnly
122+
public SentryGraphqlInstrumentation(
123+
final @Nullable BeforeSpanCallback beforeSpan,
124+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
125+
final @NotNull ExceptionReporter exceptionReporter,
126+
final @NotNull List<String> ignoredErrorTypes,
127+
final boolean enableSpecificationErrorTypeFiltering,
128+
final @NotNull String traceOrigin) {
90129
this.beforeSpan = beforeSpan;
91130
this.subscriptionHandler = subscriptionHandler;
92131
this.exceptionReporter = exceptionReporter;
93132
this.ignoredErrorTypes = ignoredErrorTypes;
133+
this.enableSpecificationErrorTypeFiltering = enableSpecificationErrorTypeFiltering;
94134
this.traceOrigin = traceOrigin;
95135
}
96136

@@ -166,6 +206,9 @@ private boolean isIgnored(final @Nullable GraphQLError error) {
166206
if (isIgnored(errorType.toString())) {
167207
return true;
168208
}
209+
if (!enableSpecificationErrorTypeFiltering) {
210+
return false;
211+
}
169212
try {
170213
final @Nullable Object specification = errorType.toSpecification(error);
171214
if (specification instanceof String && isIgnored((String) specification)) {

‎sentry-graphql-core/src/test/kotlin/io/sentry/graphql/SentryGraphqlInstrumentationTest.kt‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class SentryGraphqlInstrumentationTest {
3939
}
4040

4141
@Test
42-
fun `ignores error by classification specification type`() {
42+
fun `captures error by classification specification type when filtering is disabled`() {
4343
captureError(
4444
error(
4545
CustomErrorClassification(
@@ -50,6 +50,35 @@ class SentryGraphqlInstrumentationTest {
5050
ignoredErrorTypes = listOf("ExtendedValidationError"),
5151
)
5252

53+
verify(exceptionReporter).captureThrowable(any(), any(), any())
54+
}
55+
56+
@Test
57+
fun `does not inspect classification specification when filtering is disabled`() {
58+
val classification = mock<ErrorClassification>()
59+
whenever(classification.toString()).thenReturn("SOME_ERROR")
60+
61+
captureError(
62+
error(classification),
63+
ignoredErrorTypes = listOf("SPECIFICATION_ERROR"),
64+
)
65+
66+
verify(classification, never()).toSpecification(any())
67+
}
68+
69+
@Test
70+
fun `ignores error by classification specification type when filtering is enabled`() {
71+
captureError(
72+
error(
73+
CustomErrorClassification(
74+
"graphql.validation.interpolation.ResourceBundleMessageInterpolator\$ValidationErrorType@1",
75+
mapOf("type" to "ExtendedValidationError"),
76+
)
77+
),
78+
ignoredErrorTypes = listOf("ExtendedValidationError"),
79+
enableSpecificationErrorTypeFiltering = true,
80+
)
81+
5382
verify(exceptionReporter, never()).captureThrowable(any(), any(), any())
5483
}
5584

@@ -58,35 +87,48 @@ class SentryGraphqlInstrumentationTest {
5887
captureError(
5988
error(CustomErrorClassification("SOME_ERROR", "SPECIFICATION_ERROR")),
6089
ignoredErrorTypes = listOf("SPECIFICATION_ERROR"),
90+
enableSpecificationErrorTypeFiltering = true,
6191
)
6292

6393
verify(exceptionReporter, never()).captureThrowable(any(), any(), any())
6494
}
6595

6696
@Test
6797
fun `captures error when classification specification has no type`() {
68-
captureError(error(CustomErrorClassification("SOME_ERROR", mapOf("constraint" to "@Size"))))
98+
captureError(
99+
error(CustomErrorClassification("SOME_ERROR", mapOf("constraint" to "@Size"))),
100+
enableSpecificationErrorTypeFiltering = true,
101+
)
69102

70103
verify(exceptionReporter).captureThrowable(any(), any(), any())
71104
}
72105

73106
@Test
74107
fun `captures error when classification specification is null`() {
75-
captureError(error(CustomErrorClassification("SOME_ERROR", null)))
108+
captureError(
109+
error(CustomErrorClassification("SOME_ERROR", null)),
110+
enableSpecificationErrorTypeFiltering = true,
111+
)
76112

77113
verify(exceptionReporter).captureThrowable(any(), any(), any())
78114
}
79115

80116
@Test
81117
fun `captures error when classification specification type is unexpected`() {
82-
captureError(error(CustomErrorClassification("SOME_ERROR", mapOf("type" to 42))))
118+
captureError(
119+
error(CustomErrorClassification("SOME_ERROR", mapOf("type" to 42))),
120+
enableSpecificationErrorTypeFiltering = true,
121+
)
83122

84123
verify(exceptionReporter).captureThrowable(any(), any(), any())
85124
}
86125

87126
@Test
88127
fun `captures error when classification specification throws`() {
89-
captureError(error(ThrowingErrorClassification))
128+
captureError(
129+
error(ThrowingErrorClassification),
130+
enableSpecificationErrorTypeFiltering = true,
131+
)
90132

91133
verify(exceptionReporter).captureThrowable(any(), any(), any())
92134
}
@@ -107,18 +149,24 @@ class SentryGraphqlInstrumentationTest {
107149
captureError(
108150
error(CustomErrorClassification("SOME_ERROR", mapOf("type" to "SPECIFICATION_ERROR"))),
109151
ignoredErrorTypes = listOf("OTHER_ERROR"),
152+
enableSpecificationErrorTypeFiltering = true,
110153
)
111154

112155
verify(exceptionReporter).captureThrowable(any(), any(), any())
113156
}
114157

115-
private fun captureError(error: GraphQLError, ignoredErrorTypes: List<String> = emptyList()) {
158+
private fun captureError(
159+
error: GraphQLError,
160+
ignoredErrorTypes: List<String> = emptyList(),
161+
enableSpecificationErrorTypeFiltering: Boolean = false,
162+
) {
116163
val instrumentation =
117164
SentryGraphqlInstrumentation(
118165
null,
119166
NoOpSubscriptionHandler.getInstance(),
120167
exceptionReporter,
121168
ignoredErrorTypes,
169+
enableSpecificationErrorTypeFiltering,
122170
"manual.test",
123171
)
124172
val result = ExecutionResultImpl.newExecutionResult().addError(error).build()

‎sentry-graphql/api/sentry-graphql.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ public final class io/sentry/graphql/SentryInstrumentation : graphql/execution/i
77
public static final field SENTRY_EXCEPTIONS_CONTEXT_KEY Ljava/lang/String;
88
public static final field SENTRY_SCOPES_CONTEXT_KEY Ljava/lang/String;
99
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;)V
10+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Lio/sentry/graphql/ExceptionReporter;Ljava/util/List;Z)V
1011
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
1112
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;)V
13+
public fun <init> (Lio/sentry/graphql/SentryGraphqlInstrumentation$BeforeSpanCallback;Lio/sentry/graphql/SentrySubscriptionHandler;ZLjava/util/List;Z)V
1214
public fun <init> (Lio/sentry/graphql/SentrySubscriptionHandler;Z)V
1315
public fun beginExecuteOperation (Lgraphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters;)Lgraphql/execution/instrumentation/InstrumentationContext;
1416
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,35 @@ public SentryInstrumentation(
7474
final @NotNull SentrySubscriptionHandler subscriptionHandler,
7575
final boolean captureRequestBodyForNonSubscriptions,
7676
final @NotNull List<String> ignoredErrorTypes) {
77+
this(
78+
beforeSpan,
79+
subscriptionHandler,
80+
captureRequestBodyForNonSubscriptions,
81+
ignoredErrorTypes,
82+
false);
83+
}
84+
85+
/**
86+
* @param beforeSpan callback when a span is created
87+
* @param subscriptionHandler can report subscription errors
88+
* @param captureRequestBodyForNonSubscriptions false if request bodies should not be captured by
89+
* this integration for query and mutation operations
90+
* @param ignoredErrorTypes list of error types that should not be captured and sent to Sentry
91+
* @param enableSpecificationErrorTypeFiltering whether ignored error types should also be matched
92+
* against {@link graphql.ErrorClassification#toSpecification(graphql.GraphQLError)}
93+
*/
94+
public SentryInstrumentation(
95+
final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan,
96+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
97+
final boolean captureRequestBodyForNonSubscriptions,
98+
final @NotNull List<String> ignoredErrorTypes,
99+
final boolean enableSpecificationErrorTypeFiltering) {
77100
this(
78101
beforeSpan,
79102
subscriptionHandler,
80103
new ExceptionReporter(captureRequestBodyForNonSubscriptions),
81-
ignoredErrorTypes);
104+
ignoredErrorTypes,
105+
enableSpecificationErrorTypeFiltering);
82106
}
83107

84108
@TestOnly
@@ -87,9 +111,24 @@ public SentryInstrumentation(
87111
final @NotNull SentrySubscriptionHandler subscriptionHandler,
88112
final @NotNull ExceptionReporter exceptionReporter,
89113
final @NotNull List<String> ignoredErrorTypes) {
114+
this(beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, false);
115+
}
116+
117+
@TestOnly
118+
public SentryInstrumentation(
119+
final @Nullable SentryGraphqlInstrumentation.BeforeSpanCallback beforeSpan,
120+
final @NotNull SentrySubscriptionHandler subscriptionHandler,
121+
final @NotNull ExceptionReporter exceptionReporter,
122+
final @NotNull List<String> ignoredErrorTypes,
123+
final boolean enableSpecificationErrorTypeFiltering) {
90124
this.instrumentation =
91125
new SentryGraphqlInstrumentation(
92-
beforeSpan, subscriptionHandler, exceptionReporter, ignoredErrorTypes, TRACE_ORIGIN);
126+
beforeSpan,
127+
subscriptionHandler,
128+
exceptionReporter,
129+
ignoredErrorTypes,
130+
enableSpecificationErrorTypeFiltering,
131+
TRACE_ORIGIN);
93132
SentryIntegrationPackageStorage.getInstance().addIntegration("GraphQL");
94133
}
95134

‎sentry-spring-boot-4/api/sentry-spring-boot-4.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public class io/sentry/spring/boot4/SentryProperties$Clients {
7474
public class io/sentry/spring/boot4/SentryProperties$Graphql {
7575
public fun <init> ()V
7676
public fun getIgnoredErrorTypes ()Ljava/util/List;
77+
public fun isEnableSpecificationErrorTypeFiltering ()Z
78+
public fun setEnableSpecificationErrorTypeFiltering (Z)V
7779
public fun setIgnoredErrorTypes (Ljava/util/List;)V
7880
}
7981

0 commit comments

Comments
 (0)