From 1972a952cc9e5f94dce37f949e5e903c6dfb7eb1 Mon Sep 17 00:00:00 2001 From: Koen Punt Date: Thu, 17 Aug 2023 01:48:45 +0200 Subject: [PATCH 1/7] add instrumentation for graphql-java 21 --- .../graphql-java-21.0/build.gradle | 31 +++ .../graphql/GraphQLErrorHandler.java | 56 ++++ .../graphql/GraphQLObfuscator.java | 48 ++++ .../graphql/GraphQLOperationDefinition.java | 34 +++ .../graphql/GraphQLSpanUtil.java | 67 +++++ .../graphql/GraphQLTransactionName.java | 150 +++++++++++ .../com/nr/instrumentation/graphql/Utils.java | 14 + .../ExecutionStrategy_Instrumentation.java | 48 ++++ .../java/graphql/GraphQL_Instrumentation.java | 24 ++ .../ParseAndValidate_Instrumentation.java | 51 ++++ .../graphql/GraphQLObfuscatorTest.java | 37 +++ .../graphql/GraphQLSpanUtilTest.java | 130 ++++++++++ .../graphql/GraphQLTransactionNameTest.java | 34 +++ .../graphql/helper/GraphQLTestHelper.java | 34 +++ .../graphql/helper/PrivateApiStub.java | 99 +++++++ .../graphql/GraphQL_InstrumentationTest.java | 245 ++++++++++++++++++ .../test/resources/distributed_tracing.yml | 3 + .../federatedSubGraphQuery.gql | 7 + .../federatedSubGraphQueryObfuscated.gql | 7 + .../obfuscate-query-test-data.csv | 8 + .../queryMultiLevelAliasArg.gql | 24 ++ .../queryMultiLevelAliasArgObfuscated.gql | 24 ++ .../queryWithNameAndArg.gql | 5 + .../queryWithNameAndArgObfuscated.gql | 5 + .../obfuscateQueryTestData/simpleMutation.gql | 13 + .../simpleMutationObfuscated.gql | 13 + .../unionTypesInlineFragmentsQuery.gql | 11 + ...ionTypesInlineFragmentsQueryObfuscated.gql | 11 + .../deepestUniquePathQuery.gql | 14 + .../deepestUniqueSinglePathQuery.gql | 7 + .../federatedSubGraphQuery.gql | 7 + .../transactionNameTestData/fragments.gql | 16 ++ .../transactionNameTestData/inputTypes.gql | 6 + .../multipleOperations.gql | 18 ++ .../transactionNameTestData/schemaQuery.gql | 7 + .../simpleAnonymousQuery.gql | 10 + .../simpleMutation.gql | 13 + .../transactionNameTestData/simpleQuery.gql | 10 + .../transaction-name-test-data.csv | 17 ++ .../twoTopLevelNames.gql | 8 + .../unionTypesAndInlineFragmentQuery.gql | 8 + .../unionTypesAndInlineFragmentsQuery.gql | 11 + .../validationErrors.gql | 9 + .../variablesInsideFragments.gql | 20 ++ settings.gradle | 1 + 45 files changed, 1415 insertions(+) create mode 100644 instrumentation/graphql-java-21.0/build.gradle create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java create mode 100644 instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java create mode 100644 instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/distributed_tracing.yml create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQueryObfuscated.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/obfuscate-query-test-data.csv create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArg.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArgObfuscated.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArg.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArgObfuscated.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutation.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutationObfuscated.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQueryObfuscated.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniquePathQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniqueSinglePathQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/federatedSubGraphQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/fragments.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/inputTypes.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/multipleOperations.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/schemaQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleAnonymousQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleMutation.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/transaction-name-test-data.csv create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/twoTopLevelNames.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentsQuery.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/validationErrors.gql create mode 100644 instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/variablesInsideFragments.gql diff --git a/instrumentation/graphql-java-21.0/build.gradle b/instrumentation/graphql-java-21.0/build.gradle new file mode 100644 index 0000000000..941b7ace2c --- /dev/null +++ b/instrumentation/graphql-java-21.0/build.gradle @@ -0,0 +1,31 @@ +dependencies { + implementation(project(":agent-bridge")) + + implementation 'com.graphql-java:graphql-java:21.0' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.2' + testImplementation 'org.mockito:mockito-core:4.6.1' + testImplementation 'org.mockito:mockito-junit-jupiter:4.6.1' + + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.2'} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.graphql-java-21.0' } +} + +verifyInstrumentation { + passesOnly 'com.graphql-java:graphql-java:[21.0,)' + excludeRegex 'com.graphql-java:graphql-java:(0.0.0|201|202).*' + excludeRegex 'com.graphql-java:graphql-java:.*(vTEST|-beta|-alpha1|-nf-execution|-rc|-TEST).*' +} + +site { + title 'GraphQL Java' + type 'Framework' +} + +test { + useJUnitPlatform() +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java new file mode 100644 index 0000000000..24cb7c4352 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import com.newrelic.api.agent.NewRelic; +import graphql.ExecutionResult; +import graphql.GraphQLError; +import graphql.GraphQLException; +import graphql.GraphqlErrorException; +import graphql.execution.FieldValueInfo; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +public class GraphQLErrorHandler { + public static void reportNonNullableExceptionToNR(FieldValueInfo result) { + CompletableFuture exceptionResult = result.getFieldValue(); + if (resultHasException(exceptionResult)) { + reportExceptionFromCompletedExceptionally(exceptionResult); + } + } + + public static void reportGraphQLException(GraphQLException exception) { + NewRelic.noticeError(exception); + } + + public static void reportGraphQLError(GraphQLError error) { + NewRelic.noticeError(throwableFromGraphQLError(error)); + } + + private static boolean resultHasException(CompletableFuture exceptionResult) { + return exceptionResult != null && exceptionResult.isCompletedExceptionally(); + } + + private static void reportExceptionFromCompletedExceptionally(CompletableFuture exceptionResult) { + try { + exceptionResult.get(); + } catch (InterruptedException e) { + NewRelic.getAgent().getLogger().log(Level.FINEST, "Could not report GraphQL exception."); + } catch (ExecutionException e) { + NewRelic.noticeError(e.getCause()); + } + } + + private static Throwable throwableFromGraphQLError(GraphQLError error) { + return GraphqlErrorException.newErrorException() + .message(error.getMessage()) + .build(); + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java new file mode 100644 index 0000000000..cf87b05286 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import graphql.com.google.common.base.Joiner; + +import java.util.regex.Pattern; + +public class GraphQLObfuscator { + private static final String SINGLE_QUOTE = "'(?:[^']|'')*?(?:\\\\'.*|'(?!'))"; + private static final String DOUBLE_QUOTE = "\"(?:[^\"]|\"\")*?(?:\\\\\".*|\"(?!\"))"; + private static final String COMMENT = "(?:#|--).*?(?=\\r|\\n|$)"; + private static final String MULTILINE_COMMENT = "/\\*(?:[^/]|/[^*])*?(?:\\*/|/\\*.*)"; + private static final String UUID = "\\{?(?:[0-9a-f]\\-*){32}\\}?"; + private static final String HEX = "0x[0-9a-f]+"; + private static final String BOOLEAN = "\\b(?:true|false|null)\\b"; + private static final String NUMBER = "-?\\b(?:[0-9]+\\.)?[0-9]+([eE][+-]?[0-9]+)?"; + + private static final Pattern ALL_DIALECTS_PATTERN; + private static final Pattern ALL_UNMATCHED_PATTERN; + + static { + String allDialectsPattern = Joiner.on("|").join(SINGLE_QUOTE, DOUBLE_QUOTE, UUID, HEX, + MULTILINE_COMMENT, COMMENT, NUMBER, BOOLEAN); + + ALL_DIALECTS_PATTERN = Pattern.compile(allDialectsPattern, Pattern.DOTALL | Pattern.CASE_INSENSITIVE); + ALL_UNMATCHED_PATTERN = Pattern.compile("'|\"|/\\*|\\*/|\\$", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); + } + + public static String obfuscate(final String query) { + if (query == null || query.length() == 0) { + return query; + } + String obfuscatedQuery = ALL_DIALECTS_PATTERN.matcher(query).replaceAll("***"); + return checkForUnmatchedPairs(obfuscatedQuery); + } + + private static String checkForUnmatchedPairs(final String obfuscatedQuery) { + return GraphQLObfuscator.ALL_UNMATCHED_PATTERN.matcher(obfuscatedQuery).find() ? "***" : obfuscatedQuery; + } +} + + diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java new file mode 100644 index 0000000000..7e8aa048bc --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import graphql.language.Document; +import graphql.language.OperationDefinition; + +import java.util.List; + +public class GraphQLOperationDefinition { + private final static String DEFAULT_OPERATION_DEFINITION_NAME = ""; + private final static String DEFAULT_OPERATION_NAME = ""; + + // Multiple operations are supported for transaction name only + // The underlying library does not seem to support multiple operations at time of this instrumentation + public static OperationDefinition firstFrom(final Document document) { + List operationDefinitions = document.getDefinitionsOfType(OperationDefinition.class); + return operationDefinitions.isEmpty() ? null : operationDefinitions.get(0); + } + + public static String getOperationNameFrom(final OperationDefinition operationDefinition) { + return operationDefinition.getName() != null ? operationDefinition.getName() : DEFAULT_OPERATION_DEFINITION_NAME; + } + + public static String getOperationTypeFrom(final OperationDefinition operationDefinition) { + OperationDefinition.Operation operation = operationDefinition.getOperation(); + return operation != null ? operation.name() : DEFAULT_OPERATION_NAME; + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java new file mode 100644 index 0000000000..b63647bcbd --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import com.newrelic.agent.bridge.AgentBridge; +import graphql.execution.ExecutionStrategyParameters; +import graphql.language.Document; +import graphql.language.OperationDefinition; +import graphql.schema.GraphQLNamedSchemaElement; +import graphql.schema.GraphQLOutputType; + +import static com.nr.instrumentation.graphql.GraphQLObfuscator.obfuscate; +import static com.nr.instrumentation.graphql.GraphQLOperationDefinition.getOperationTypeFrom; +import static com.nr.instrumentation.graphql.Utils.getValueOrDefault; + +public class GraphQLSpanUtil { + + private final static String DEFAULT_OPERATION_TYPE = "Unavailable"; + private final static String DEFAULT_OPERATION_NAME = ""; + + public static void setOperationAttributes(final Document document, final String query) { + String nonNullQuery = getValueOrDefault(query, ""); + if (document == null) { + setDefaultOperationAttributes(nonNullQuery); + return; + } + OperationDefinition definition = GraphQLOperationDefinition.firstFrom(document); + if (definition == null) { + setDefaultOperationAttributes(nonNullQuery); + } else { + setOperationAttributes(getOperationTypeFrom(definition), definition.getName(), nonNullQuery); + } + } + + public static void setResolverAttributes(ExecutionStrategyParameters parameters) { + AgentBridge.privateApi.addTracerParameter("graphql.field.path", parameters.getPath().getSegmentName()); + AgentBridge.privateApi.addTracerParameter("graphql.field.name", parameters.getField().getName()); + // ExecutionStepInfo is not nullable according to documentation + GraphQLOutputType graphQLOutputType = parameters.getExecutionStepInfo().getType(); + setGraphQLFieldParentTypeIfPossible(graphQLOutputType); + } + + private static void setGraphQLFieldParentTypeIfPossible(GraphQLOutputType graphQLOutputType) { + // graphql.field.parentType is NOT required according to the Agent Spec + if (graphQLOutputType instanceof GraphQLNamedSchemaElement) { + GraphQLNamedSchemaElement named = (GraphQLNamedSchemaElement) graphQLOutputType; + AgentBridge.privateApi.addTracerParameter("graphql.field.parentType", named.getName()); + } + } + + private static void setOperationAttributes(String type, String name, String query) { + AgentBridge.privateApi.addTracerParameter("graphql.operation.type", getValueOrDefault(type, DEFAULT_OPERATION_TYPE)); + AgentBridge.privateApi.addTracerParameter("graphql.operation.name", getValueOrDefault(name, DEFAULT_OPERATION_NAME)); + AgentBridge.privateApi.addTracerParameter("graphql.operation.query", obfuscate(query)); + } + + private static void setDefaultOperationAttributes(String query) { + AgentBridge.privateApi.addTracerParameter("graphql.operation.type", DEFAULT_OPERATION_TYPE); + AgentBridge.privateApi.addTracerParameter("graphql.operation.name", DEFAULT_OPERATION_NAME); + AgentBridge.privateApi.addTracerParameter("graphql.operation.query", obfuscate(query)); + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java new file mode 100644 index 0000000000..66bbcbccf6 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import graphql.language.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.nr.instrumentation.graphql.Utils.isNullOrEmpty; + +/** + * Generates GraphQL transaction names based on details referenced in Node instrumentation. + * + * @see + * NewRelic Node Apollo Server Plugin - Transactions + * + *

+ * Batch queries are not supported by GraphQL Java implementation at this time + * and transaction names for parse errors must be set elsewhere because this class + * relies on the GraphQL Document that is the artifact of a successful parse. + */ +public class GraphQLTransactionName { + + private final static String DEFAULT_TRANSACTION_NAME = ""; + + // federated field names to exclude from path calculations + private final static String TYPENAME = "__typename"; + private final static String ID = "id"; + + /** + * Generates a transaction name based on a valid, parsed GraphQL Document + * + * @param document parsed GraphQL Document + * @return a transaction name based on given document + */ + public static String from(final Document document) { + if (document == null) return DEFAULT_TRANSACTION_NAME; + List operationDefinitions = document.getDefinitionsOfType(OperationDefinition.class); + if (isNullOrEmpty(operationDefinitions)) return DEFAULT_TRANSACTION_NAME; + if (operationDefinitions.size() == 1) { + return getTransactionNameFor(operationDefinitions.get(0)); + } + return "/batch" + operationDefinitions.stream() + .map(GraphQLTransactionName::getTransactionNameFor) + .collect(Collectors.joining()); + } + + private static String getTransactionNameFor(OperationDefinition operationDefinition) { + if (operationDefinition == null) return DEFAULT_TRANSACTION_NAME; + return createBeginningOfTransactionNameFrom(operationDefinition) + + createEndOfTransactionNameFrom(operationDefinition.getSelectionSet()); + } + + private static String createBeginningOfTransactionNameFrom(final OperationDefinition operationDefinition) { + String operationType = GraphQLOperationDefinition.getOperationTypeFrom(operationDefinition); + String operationName = GraphQLOperationDefinition.getOperationNameFrom(operationDefinition); + return String.format("/%s/%s", operationType, operationName); + } + + private static String createEndOfTransactionNameFrom(final SelectionSet selectionSet) { + Selection selection = onlyNonFederatedSelectionOrNoneFrom(selectionSet); + if (selection == null) return ""; + List selections = new ArrayList<>(); + while (selection != null) { + selections.add(selection); + selection = nextNonFederatedSelectionChildFrom(selection); + } + return createPathSuffixFrom(selections); + } + + private static String createPathSuffixFrom(final List selections) { + if (selections == null || selections.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder("/").append(getNameFrom(selections.get(0))); + int length = selections.size(); + // skip first element, it is already added without extra formatting + for (int i = 1; i < length; i++) { + sb.append(getFormattedNameFor(selections.get(i))); + } + return sb.toString(); + } + + private static String getFormattedNameFor(Selection selection) { + if (selection instanceof Field) { + return String.format(".%s", getNameFrom((Field) selection)); + } + if (selection instanceof InlineFragment) { + return String.format("<%s>", getNameFrom((InlineFragment) selection)); + } + return ""; + } + + private static Selection onlyNonFederatedSelectionOrNoneFrom(final SelectionSet selectionSet) { + if (selectionSet == null) { + return null; + } + List selections = selectionSet.getSelections(); + if (isNullOrEmpty(selections)) { + return null; + } + List selection = selections.stream() + .filter(namedNode -> notFederatedFieldName(getNameFrom(namedNode))) + .collect(Collectors.toList()); + // there can be only one, or we stop digging into query + return selection.size() == 1 ? selection.get(0) : null; + } + + private static String getNameFrom(final Selection selection) { + if (selection instanceof Field) { + return getNameFrom((Field) selection); + } + if (selection instanceof InlineFragment) { + return getNameFrom((InlineFragment) selection); + } + // FragmentSpread also implements Selection but not sure how that might apply here + return null; + } + + private static String getNameFrom(final Field field) { + return field.getName(); + } + + private static String getNameFrom(final InlineFragment inlineFragment) { + TypeName typeCondition = inlineFragment.getTypeCondition(); + if (typeCondition != null) { + return typeCondition.getName(); + } + return ""; + } + + private static Selection nextNonFederatedSelectionChildFrom(final Selection selection) { + if (!(selection instanceof SelectionSetContainer)) { + return null; + } + SelectionSet selectionSet = ((SelectionSetContainer) selection).getSelectionSet(); + return onlyNonFederatedSelectionOrNoneFrom(selectionSet); + } + + private static boolean notFederatedFieldName(final String fieldName) { + return !(TYPENAME.equals(fieldName) || ID.equals(fieldName)); + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java new file mode 100644 index 0000000000..9a1747e054 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java @@ -0,0 +1,14 @@ +package com.nr.instrumentation.graphql; + +import java.util.Collection; + +// instead of adding dependencies, just add some utility methods +public class Utils { + public static T getValueOrDefault(T value, T defaultValue) { + return value == null ? defaultValue : value; + } + + public static boolean isNullOrEmpty(final Collection c) { + return c == null || c.isEmpty(); + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java new file mode 100644 index 0000000000..78102128cf --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package graphql; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import graphql.execution.ExecutionContext; +import graphql.execution.ExecutionStrategyParameters; +import graphql.execution.FieldValueInfo; +import graphql.schema.DataFetchingEnvironment; + +import java.util.concurrent.CompletableFuture; + +import static com.nr.instrumentation.graphql.GraphQLSpanUtil.*; +import static com.nr.instrumentation.graphql.GraphQLErrorHandler.*; + +@Weave(originalName = "graphql.execution.ExecutionStrategy", type = MatchType.BaseClass) +public class ExecutionStrategy_Instrumentation { + + @Trace + protected CompletableFuture resolveFieldWithInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { + + NewRelic.getAgent().getTracedMethod().setMetricName("GraphQL/resolve/" + parameters.getPath().getSegmentName()); + setResolverAttributes(parameters); + return Weaver.callOriginal(); + } + + protected CompletableFuture handleFetchingException(ExecutionContext executionContext, DataFetchingEnvironment environment, Throwable e) { + NewRelic.noticeError(e); + return Weaver.callOriginal(); + } + + protected FieldValueInfo completeValue(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { + FieldValueInfo result = Weaver.callOriginal(); + if (result != null) { + reportNonNullableExceptionToNR(result); + } + return result; + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java new file mode 100644 index 0000000000..374bd487ec --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java @@ -0,0 +1,24 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package graphql; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.util.concurrent.CompletableFuture; + +@Weave(originalName = "graphql.GraphQL", type = MatchType.ExactClass) +public class GraphQL_Instrumentation { + + @Trace + public CompletableFuture executeAsync(ExecutionInput executionInput) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java new file mode 100644 index 0000000000..c616132493 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package graphql; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.graphql.GraphQLTransactionName; +import graphql.language.Document; +import graphql.schema.GraphQLSchema; +import graphql.validation.ValidationError; + +import java.util.List; + +import static com.nr.instrumentation.graphql.GraphQLSpanUtil.*; +import static com.nr.instrumentation.graphql.GraphQLErrorHandler.*; + +@Weave(originalName = "graphql.ParseAndValidate", type = MatchType.ExactClass) +public class ParseAndValidate_Instrumentation { + + public static ParseAndValidateResult parse(ExecutionInput executionInput) { + ParseAndValidateResult result = Weaver.callOriginal(); + if (result != null) { + String transactionName = GraphQLTransactionName.from(result.getDocument()); + NewRelic.getAgent().getTracedMethod().setMetricName("GraphQL/operation" + transactionName); + setOperationAttributes(result.getDocument(), executionInput.getQuery()); + + if (result.isFailure()) { + reportGraphQLException(result.getSyntaxException()); + NewRelic.setTransactionName("GraphQL", "*"); + } else { + NewRelic.setTransactionName("GraphQL", transactionName); + } + } + return result; + } + + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument) { + List errors = Weaver.callOriginal(); + if (errors != null && !errors.isEmpty()) { + reportGraphQLError(errors.get(0)); + } + return errors; + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java new file mode 100644 index 0000000000..003baf1050 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +import static com.nr.instrumentation.graphql.helper.GraphQLTestHelper.readText; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GraphQLObfuscatorTest { + + private final static String OBFUSCATE_DATA_DIR = "obfuscateQueryTestData"; + + @ParameterizedTest + @CsvFileSource(resources = "/obfuscateQueryTestData/obfuscate-query-test-data.csv", delimiter = '|', numLinesToSkip = 2) + public void testObfuscateQuery(String queryToObfuscateFilename, String expectedObfuscatedQueryFilename) { + //setup + queryToObfuscateFilename = queryToObfuscateFilename.trim(); + expectedObfuscatedQueryFilename = expectedObfuscatedQueryFilename.trim(); + String expectedObfuscatedResult = readText(OBFUSCATE_DATA_DIR, expectedObfuscatedQueryFilename); + + //given + String query = readText(OBFUSCATE_DATA_DIR, queryToObfuscateFilename); + + //when + String obfuscatedQuery = GraphQLObfuscator.obfuscate(query); + + //then + assertEquals(expectedObfuscatedResult, obfuscatedQuery); + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java new file mode 100644 index 0000000000..f61178b9c0 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java @@ -0,0 +1,130 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.PrivateApi; +import com.nr.instrumentation.graphql.helper.GraphQLTestHelper; +import com.nr.instrumentation.graphql.helper.PrivateApiStub; +import graphql.execution.ExecutionStepInfo; +import graphql.execution.ExecutionStrategyParameters; +import graphql.execution.MergedField; +import graphql.execution.ResultPath; +import graphql.language.Definition; +import graphql.language.Document; +import graphql.schema.GraphQLNonNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GraphQLSpanUtilTest { + + private static final List NO_DEFINITIONS = Collections.emptyList(); + + private PrivateApiStub privateApiStub; + private PrivateApi privateApi; + + private static Stream providerForTestEdges() { + return Stream.of( + Arguments.of(null, null, "Unavailable", "", ""), + Arguments.of(null, "{ hello }", "Unavailable", "", "{ hello }"), + Arguments.of(new Document(NO_DEFINITIONS), "", "Unavailable", "", ""), + Arguments.of(new Document(NO_DEFINITIONS), null, "Unavailable", "", "") + ); + } + + @BeforeEach + public void beforeEachTest() { + privateApi = AgentBridge.privateApi; + privateApiStub = new PrivateApiStub(); + AgentBridge.privateApi = privateApiStub; + } + + @AfterEach + public void afterEachTest() { + AgentBridge.privateApi = privateApi; + } + + @ParameterizedTest + @CsvSource(value = { + "query simple { libraries },QUERY,simple", + "query { libraries },QUERY,", + "{ hello },QUERY,", + "mutation { data },MUTATION,", + "mutation bob { data },MUTATION,bob" + }) + public void testSetOperationAttributes(String query, String expectedType, String expectedName) { + Document document = GraphQLTestHelper.parseDocumentFromText(query); + GraphQLSpanUtil.setOperationAttributes(document, query); + + assertEquals(expectedType, privateApiStub.getTracerParameterFor("graphql.operation.type")); + assertEquals(expectedName, privateApiStub.getTracerParameterFor("graphql.operation.name")); + assertEquals(query, privateApiStub.getTracerParameterFor("graphql.operation.query")); + } + + @ParameterizedTest + @MethodSource("providerForTestEdges") + public void testSetOperationAttributesEdgeCases(Document document, String query, String expectedType, String expectedName, String expectedQuery) { + GraphQLSpanUtil.setOperationAttributes(document, query); + + assertEquals(expectedType, privateApiStub.getTracerParameterFor("graphql.operation.type")); + assertEquals(expectedName, privateApiStub.getTracerParameterFor("graphql.operation.name")); + assertEquals(expectedQuery, privateApiStub.getTracerParameterFor("graphql.operation.query")); + } + + // This test verifies https://issues.newrelic.com/browse/NEWRELIC-4936 no longer exists + @Test + public void testInvalidClassExceptionFix() { + // given execution parameters with execution step info type that is NOT GraphQLNamedSchemaElement + String expectedFieldPath = "fieldPath"; + String expectedFieldName = "fieldName"; + ExecutionStrategyParameters parameters = + mockExecutionStrategyParametersForInvalidClassCastExceptionTest(expectedFieldPath, expectedFieldName); + + // when (this had ClassCastException) + GraphQLSpanUtil.setResolverAttributes(parameters); + + // then tracer parameters set correctly without exception + assertEquals(expectedFieldPath, privateApiStub.getTracerParameterFor("graphql.field.path")); + assertEquals(expectedFieldName, privateApiStub.getTracerParameterFor("graphql.field.name")); + + // and 'graphql.field.parentType' is not set + assertNull(privateApiStub.getTracerParameterFor("graphql.field.parentType"), + "'graphql.field.parentType' is not required and should be null here"); + } + + private static ExecutionStrategyParameters mockExecutionStrategyParametersForInvalidClassCastExceptionTest(String graphqlFieldPath, String graphqlFieldName) { + ExecutionStrategyParameters parameters = mock(ExecutionStrategyParameters.class); + ResultPath resultPath = mock(ResultPath.class); + MergedField mergedField = mock(MergedField.class); + ExecutionStepInfo executionStepInfo = mock(ExecutionStepInfo.class); + GraphQLNonNull notGraphQLNamedSchemaElement = mock(GraphQLNonNull.class); + + when(resultPath.getSegmentName()).thenReturn(graphqlFieldPath); + when(mergedField.getName()).thenReturn(graphqlFieldName); + when(parameters.getPath()).thenReturn(resultPath); + when(parameters.getField()).thenReturn(mergedField); + when(parameters.getExecutionStepInfo()).thenReturn(executionStepInfo); + when(executionStepInfo.getType()).thenReturn(notGraphQLNamedSchemaElement); + + return parameters; + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java new file mode 100644 index 0000000000..ba130cc1a2 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql; + +import graphql.language.Document; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +import static com.nr.instrumentation.graphql.helper.GraphQLTestHelper.parseDocument; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GraphQLTransactionNameTest { + + private final static String TEST_DATA_DIR = "transactionNameTestData"; + + @ParameterizedTest + @CsvFileSource(resources = "/transactionNameTestData/transaction-name-test-data.csv", delimiter = '|', numLinesToSkip = 2) + public void testQuery(String testFileName, String expectedTransactionName) { + //setup + testFileName = testFileName.trim(); + expectedTransactionName = expectedTransactionName.trim(); + //given + Document document = parseDocument(TEST_DATA_DIR, testFileName); + //when + String transactionName = GraphQLTransactionName.from(document); + //then + assertEquals(expectedTransactionName, transactionName); + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java new file mode 100644 index 0000000000..9da9fdeed7 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql.helper; + +import graphql.language.Document; +import graphql.parser.Parser; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class GraphQLTestHelper { + public static Document parseDocument(String testDir, String filename) { + return Parser.parse(readText(testDir, filename)); + } + + public static String readText(String testDir, String filename) { + try { + String projectPath = String.format("src/test/resources/%s/%s.gql", testDir, filename); + return new String(Files.readAllBytes(Paths.get(projectPath))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Document parseDocumentFromText(String text) { + return Parser.parse(text); + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java new file mode 100644 index 0000000000..971076d338 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java @@ -0,0 +1,99 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.instrumentation.graphql.helper; + +import com.newrelic.agent.bridge.PrivateApi; + +import javax.management.MBeanServer; +import java.io.Closeable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class PrivateApiStub implements PrivateApi { + private final Map tracerParameters = new HashMap<>(); + + public String getTracerParameterFor(String key) { + return tracerParameters.get(key); + } + + @Override + public Closeable addSampler(Runnable sampler, int period, TimeUnit timeUnit) { + return null; + } + + @Override + public void setServerInfo(String serverInfo) { + + } + + @Override + public void addCustomAttribute(String key, Number value) { + + } + + @Override + public void addCustomAttribute(String key, Map values) { + + } + + @Override + public void addCustomAttribute(String key, String value) { + + } + + @Override + public void addTracerParameter(String key, Number value) { + + } + + @Override + public void addTracerParameter(String key, String value) { + tracerParameters.put(key, value); + } + + @Override + public void addTracerParameter(String key, Map values) { + + } + + @Override + public void addMBeanServer(MBeanServer server) { + + } + + @Override + public void removeMBeanServer(MBeanServer serverToRemove) { + + } + + @Override + public void reportHTTPError(String message, int statusCode, String uri) { + + } + + @Override + public void reportException(Throwable throwable) { + + } + + @Override + public void setAppServerPort(int port) { + + } + + @Override + public void setServerInfo(String dispatcherName, String version) { + + } + + @Override + public void setInstanceName(String instanceName) { + + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java new file mode 100644 index 0000000000..c98884a8b8 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java @@ -0,0 +1,245 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package graphql; + +import com.newrelic.agent.introspec.InstrumentationTestConfig; +import com.newrelic.agent.introspec.InstrumentationTestRunner; +import com.newrelic.agent.introspec.Introspector; +import com.newrelic.agent.introspec.SpanEvent; +import com.newrelic.api.agent.Trace; +import graphql.schema.GraphQLSchema; +import graphql.schema.StaticDataFetcher; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.stream.Collectors; + +import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring; +import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = {"graphql", "com.nr.instrumentation"}, configName = "distributed_tracing.yml") +public class GraphQL_InstrumentationTest { + private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10_000; + private static final String TEST_ARG = "testArg"; + + private static GraphQL graphQL; + + @BeforeClass + public static void initialize() { + String schema = "type Query{hello(" + TEST_ARG + ": String): String}"; + + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); + + RuntimeWiring runtimeWiring = newRuntimeWiring() + .type("Query", builder -> builder.dataFetcher("hello", + new StaticDataFetcher("world"))) + .build(); + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + + graphQL = GraphQL.newGraphQL(graphQLSchema).build(); + } + + @AfterEach + public void cleanUp() { + InstrumentationTestRunner.getIntrospector().clear(); + } + + @Test + public void queryWithNoArg() { + //given + String query = "{hello}"; + //when + trace(createRunnable(query)); + //then + assertRequestNoArg("QUERY//hello", "{hello}"); + } + + @Test + public void queryWithArg() { + //given + String query = "{hello (" + TEST_ARG + ": \"fo)o\")}"; + //when + trace(createRunnable(query)); + //then + assertRequestWithArg("QUERY//hello", "{hello (" + TEST_ARG + ": ***)}"); + } + + @Test + public void parsingException() { + //given + String query = "cause a parse error"; + //when + trace(createRunnable(query)); + //then + String expectedErrorMessage = "Invalid Syntax : offending token 'cause' at line 1 column 1"; + assertErrorOperation("*", "GraphQL/operation", + "graphql.parser.InvalidSyntaxException", expectedErrorMessage, true); + } + + @Test + public void validationException() { + //given + String query = "{noSuchField}"; + //when + trace(createRunnable(query)); + //then + String expectedErrorMessage = "Validation error of type FieldUndefined: Field 'noSuchField' in type 'Query' is undefined @ 'noSuchField'"; + assertErrorOperation("QUERY//noSuchField", + "GraphQL/operation/QUERY//noSuchField", "graphql.GraphqlErrorException", expectedErrorMessage, false); + } + + @Test + public void resolverException() { + //given + String query = "{hello " + + "\n" + + "bye}"; + + //when + trace(createRunnable(query, graphWithResolverException())); + //then + assertExceptionOnSpan("QUERY/", "GraphQL/resolve/hello", "java.lang.RuntimeException", false); + assertExceptionOnSpan("QUERY/", "GraphQL/resolve/bye", "graphql.execution.NonNullableFieldWasNullException", false); + } + + @Trace(dispatcher = true) + private void trace(Runnable runnable) { + runnable.run(); + } + + private Runnable createRunnable(final String query) { + return () -> graphQL.execute(query); + } + + private Runnable createRunnable(final String query, GraphQL graphql) { + return () -> graphql.execute(query); + } + + private GraphQL graphWithResolverException() { + String schema = "type Query{hello(" + TEST_ARG + ": String): String" + + "\n" + + "bye: String!}"; + + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); + + RuntimeWiring runtimeWiring = newRuntimeWiring() + .type(newTypeWiring("Query") + .dataFetcher("hello", environment -> { + throw new RuntimeException("waggle"); + }) + .dataFetcher("bye", environment -> null) + ) + .build(); + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + + return GraphQL.newGraphQL(graphQLSchema).build(); + } + + private void txFinishedWithExpectedName(Introspector introspector, String expectedTransactionSuffix, boolean isParseError) { + assertEquals(1, introspector.getFinishedTransactionCount(DEFAULT_TIMEOUT_IN_MILLIS)); + String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("Transaction name is incorrect", + "OtherTransaction/GraphQL/" + expectedTransactionSuffix, txName); + } + + private void attributeValueOnSpan(Introspector introspector, String spanName, String attribute, String value) { + List spanEvents = introspector.getSpanEvents().stream() + .filter(spanEvent -> spanEvent.getName().contains(spanName)) + .collect(Collectors.toList()); + Assert.assertEquals(1, spanEvents.size()); + Assert.assertNotNull(spanEvents.get(0).getAgentAttributes().get(attribute)); + Assert.assertEquals(value, spanEvents.get(0).getAgentAttributes().get(attribute)); + } + + private boolean scopedAndUnscopedMetrics(Introspector introspector, String metricPrefix) { + boolean scoped = introspector.getMetricsForTransaction(introspector.getTransactionNames().iterator().next()) + .keySet().stream().anyMatch(s -> s.contains(metricPrefix)); + boolean unscoped = introspector.getUnscopedMetrics().keySet().stream().anyMatch(s -> s.contains(metricPrefix)); + return scoped && unscoped; + } + + private void expectedMetrics(Introspector introspector) { + assertTrue(scopedAndUnscopedMetrics(introspector, "GraphQL/operation/")); + assertTrue(scopedAndUnscopedMetrics(introspector, "GraphQL/resolve/")); + } + + private void agentAttributeNotOnOtherSpans(Introspector introspector, String spanName, String attributeCategory) { + assertFalse(introspector.getSpanEvents().stream() + .filter(spanEvent -> !spanEvent.getName().contains(spanName)) + .anyMatch(spanEvent -> spanEvent.getAgentAttributes().keySet().stream().anyMatch(key -> key.contains(attributeCategory))) + ); + } + + private void resolverAttributesOnCorrectSpan(Introspector introspector) { + attributeValueOnSpan(introspector, "GraphQL/resolve", "graphql.field.parentType", "Query"); + attributeValueOnSpan(introspector, "GraphQL/resolve", "graphql.field.name", "hello"); + attributeValueOnSpan(introspector, "GraphQL/resolve", "graphql.field.path", "hello"); + agentAttributeNotOnOtherSpans(introspector, "GraphQL/resolve", "graphql.field"); + } + + private void errorAttributesOnCorrectSpan(Introspector introspector, String spanName, String errorClass, String errorMessage) { + attributeValueOnSpan(introspector, spanName, "error.class", errorClass); + attributeValueOnSpan(introspector, spanName, "error.message", errorMessage); + agentAttributeNotOnOtherSpans(introspector, spanName, "error.class"); + agentAttributeNotOnOtherSpans(introspector, spanName, "error.message"); + } + + private void operationAttributesOnCorrectSpan(Introspector introspector, String spanName) { + attributeValueOnSpan(introspector, spanName, "graphql.operation.name", ""); + attributeValueOnSpan(introspector, spanName, "graphql.operation.type", "QUERY"); + agentAttributeNotOnOtherSpans(introspector, "GraphQL/operation", "graphql.operation"); + } + + private void assertRequestNoArg(String expectedTransactionSuffix, String expectedQueryAttribute) { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + txFinishedWithExpectedName(introspector, expectedTransactionSuffix, false); + attributeValueOnSpan(introspector, expectedTransactionSuffix, "graphql.operation.query", expectedQueryAttribute); + operationAttributesOnCorrectSpan(introspector, expectedTransactionSuffix); + resolverAttributesOnCorrectSpan(introspector); + expectedMetrics(introspector); + } + + private void assertRequestWithArg(String expectedTransactionSuffix, String expectedQueryAttribute) { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + txFinishedWithExpectedName(introspector, expectedTransactionSuffix, false); + attributeValueOnSpan(introspector, expectedTransactionSuffix, "graphql.operation.query", expectedQueryAttribute); + operationAttributesOnCorrectSpan(introspector, expectedTransactionSuffix); + resolverAttributesOnCorrectSpan(introspector); + expectedMetrics(introspector); + } + + private void assertErrorOperation(String expectedTransactionSuffix, String spanName, String errorClass, String errorMessage, boolean isParseError) { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + txFinishedWithExpectedName(introspector, expectedTransactionSuffix, isParseError); + errorAttributesOnCorrectSpan(introspector, spanName, errorClass, errorMessage); + } + + private void assertExceptionOnSpan(String expectedTransactionSuffix, String spanName, String errorClass, boolean isParseError) { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + txFinishedWithExpectedName(introspector, expectedTransactionSuffix, isParseError); + attributeValueOnSpan(introspector, spanName, "error.class", errorClass); + } +} diff --git a/instrumentation/graphql-java-21.0/src/test/resources/distributed_tracing.yml b/instrumentation/graphql-java-21.0/src/test/resources/distributed_tracing.yml new file mode 100644 index 0000000000..acdf6d1a3d --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/distributed_tracing.yml @@ -0,0 +1,3 @@ +common: &default_settings + distributed_tracing: + enabled: true \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQuery.gql new file mode 100644 index 0000000000..efabfdd597 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQuery.gql @@ -0,0 +1,7 @@ +query { + libraries { + branch + __typename + id + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQueryObfuscated.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQueryObfuscated.gql new file mode 100644 index 0000000000..efabfdd597 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/federatedSubGraphQueryObfuscated.gql @@ -0,0 +1,7 @@ +query { + libraries { + branch + __typename + id + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/obfuscate-query-test-data.csv b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/obfuscate-query-test-data.csv new file mode 100644 index 0000000000..2daa6bf087 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/obfuscate-query-test-data.csv @@ -0,0 +1,8 @@ +GraphQL query filename | Expected obfuscated query file name +--------------------------------------------------------------------------------------------------- +queryWithNameAndArg | queryWithNameAndArgObfuscated +queryMultiLevelAliasArg | queryMultiLevelAliasArgObfuscated +simpleMutation | simpleMutationObfuscated +federatedSubGraphQuery | federatedSubGraphQueryObfuscated +unionTypesInlineFragmentsQuery | unionTypesInlineFragmentsQueryObfuscated + diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArg.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArg.gql new file mode 100644 index 0000000000..8ef85c82e6 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArg.gql @@ -0,0 +1,24 @@ +query { + FIRST: libraries (id: 123, name: "me bro") { + branch + booksInStock (password: "hide me") { + title (id: 123), + author + } + bathroomReading: magazinesInStock (password: "hide me") { + magissue, + magtitle + } + } + SECOND: Slibraries (id: 456, name: "no bro") { + Sbranch + profitCenter: SbooksInStock (password: "hide me") { + Sisbn, + Stitle, + } + SmagazinesInStock (password: "hide me") { + Smagissue, + Smagtitle + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArgObfuscated.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArgObfuscated.gql new file mode 100644 index 0000000000..362ca346e3 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryMultiLevelAliasArgObfuscated.gql @@ -0,0 +1,24 @@ +query { + FIRST: libraries (id: ***, name: ***) { + branch + booksInStock (password: ***) { + title (id: ***), + author + } + bathroomReading: magazinesInStock (password: ***) { + magissue, + magtitle + } + } + SECOND: Slibraries (id: ***, name: ***) { + Sbranch + profitCenter: SbooksInStock (password: ***) { + Sisbn, + Stitle, + } + SmagazinesInStock (password: ***) { + Smagissue, + Smagtitle + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArg.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArg.gql new file mode 100644 index 0000000000..6c8558d443 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArg.gql @@ -0,0 +1,5 @@ +query fastAndFun { + bookById (id: "book-1") { + title + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArgObfuscated.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArgObfuscated.gql new file mode 100644 index 0000000000..cb7c97d674 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/queryWithNameAndArgObfuscated.gql @@ -0,0 +1,5 @@ +query fastAndFun { + bookById (id: ***) { + title + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutation.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutation.gql new file mode 100644 index 0000000000..aba5da9945 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutation.gql @@ -0,0 +1,13 @@ +mutation { + writePost(title: "New Post2", text: "Text", category: null, author: "Author2") { + id + title + category + text + author { + id + name + thumbnail + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutationObfuscated.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutationObfuscated.gql new file mode 100644 index 0000000000..a6c316a8d9 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/simpleMutationObfuscated.gql @@ -0,0 +1,13 @@ +mutation { + writePost(title: ***, text: ***, category: ***, author: ***) { + id + title + category + text + author { + id + name + thumbnail + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQuery.gql new file mode 100644 index 0000000000..d61192b0be --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQuery.gql @@ -0,0 +1,11 @@ +query example { + search(contains: "author") { + __typename + ... on Author { + name + } + ... on Book { + title + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQueryObfuscated.gql b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQueryObfuscated.gql new file mode 100644 index 0000000000..baf1f4d47d --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/obfuscateQueryTestData/unionTypesInlineFragmentsQueryObfuscated.gql @@ -0,0 +1,11 @@ +query example { + search(contains: ***) { + __typename + ... on Author { + name + } + ... on Book { + title + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniquePathQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniquePathQuery.gql new file mode 100644 index 0000000000..00b53e9646 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniquePathQuery.gql @@ -0,0 +1,14 @@ +query { + libraries { + branch + booksInStock { + isbn, + title, + author + } + magazinesInStock { + issue, + title + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniqueSinglePathQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniqueSinglePathQuery.gql new file mode 100644 index 0000000000..a00e672829 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/deepestUniqueSinglePathQuery.gql @@ -0,0 +1,7 @@ +query { + libraries { + booksInStock { + title + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/federatedSubGraphQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/federatedSubGraphQuery.gql new file mode 100644 index 0000000000..efabfdd597 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/federatedSubGraphQuery.gql @@ -0,0 +1,7 @@ +query { + libraries { + branch + __typename + id + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/fragments.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/fragments.gql new file mode 100644 index 0000000000..33a22547df --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/fragments.gql @@ -0,0 +1,16 @@ +{ + leftComparison: hero(episode: EMPIRE) { + ...comparisonFields + } + rightComparison: hero(episode: JEDI) { + ...comparisonFields + } +} + +fragment comparisonFields on Character { + name + appearsIn + friends { + name + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/inputTypes.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/inputTypes.gql new file mode 100644 index 0000000000..cf73d7a8ec --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/inputTypes.gql @@ -0,0 +1,6 @@ +mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { + createReview(episode: $ep, review: $review) { + stars + commentary + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/multipleOperations.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/multipleOperations.gql new file mode 100644 index 0000000000..3b49a13cdc --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/multipleOperations.gql @@ -0,0 +1,18 @@ +query getTaskAndUser { + getTask(id: "0x3") { + id + title + completed + } + queryUser(filter: {username: {eq: "dgraphlabs"}}) { + username + name + } +} + +query completedTasks { + queryTask(filter: {completed: true}) { + title + completed + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/schemaQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/schemaQuery.gql new file mode 100644 index 0000000000..312009114d --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/schemaQuery.gql @@ -0,0 +1,7 @@ +{ + __schema { + types { + name + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleAnonymousQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleAnonymousQuery.gql new file mode 100644 index 0000000000..e027f659c0 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleAnonymousQuery.gql @@ -0,0 +1,10 @@ +query { + libraries { + books { + title + author { + name + } + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleMutation.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleMutation.gql new file mode 100644 index 0000000000..aba5da9945 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleMutation.gql @@ -0,0 +1,13 @@ +mutation { + writePost(title: "New Post2", text: "Text", category: null, author: "Author2") { + id + title + category + text + author { + id + name + thumbnail + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleQuery.gql new file mode 100644 index 0000000000..214b292899 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/simpleQuery.gql @@ -0,0 +1,10 @@ +query simple { + libraries { + books { + title + author { + name + } + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/transaction-name-test-data.csv b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/transaction-name-test-data.csv new file mode 100644 index 0000000000..1cdb07e7c7 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/transaction-name-test-data.csv @@ -0,0 +1,17 @@ +GraphQL query filename | Expected transaction name +--------------------------------------------------------------------------------------------------- +simpleQuery | /QUERY/simple/libraries.books +simpleAnonymousQuery | /QUERY//libraries.books +deepestUniquePathQuery | /QUERY//libraries +deepestUniqueSinglePathQuery | /QUERY//libraries.booksInStock.title +federatedSubGraphQuery | /QUERY//libraries.branch +unionTypesAndInlineFragmentsQuery | /QUERY/example/search +validationErrors | /QUERY/GetBooksByLibrary/libraries.books.doesnotexist.name +unionTypesAndInlineFragmentQuery | /QUERY/example/search.name +simpleMutation | /MUTATION//writePost +twoTopLevelNames | /QUERY/ +fragments | /QUERY/ +variablesInsideFragments | /QUERY/HeroComparison +inputTypes | /MUTATION/CreateReviewForEpisode/createReview +schemaQuery | /QUERY//__schema.types.name +multipleOperations | /batch/QUERY/getTaskAndUser/QUERY/completedTasks/queryTask diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/twoTopLevelNames.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/twoTopLevelNames.gql new file mode 100644 index 0000000000..286925129b --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/twoTopLevelNames.gql @@ -0,0 +1,8 @@ +query { + libraries { + branch + } + gyms { + branch + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentQuery.gql new file mode 100644 index 0000000000..029d987c55 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentQuery.gql @@ -0,0 +1,8 @@ +query example { + search(contains: "author") { + __typename + ... on Author { + name + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentsQuery.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentsQuery.gql new file mode 100644 index 0000000000..d61192b0be --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/unionTypesAndInlineFragmentsQuery.gql @@ -0,0 +1,11 @@ +query example { + search(contains: "author") { + __typename + ... on Author { + name + } + ... on Book { + title + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/validationErrors.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/validationErrors.gql new file mode 100644 index 0000000000..8928d56ddc --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/validationErrors.gql @@ -0,0 +1,9 @@ +query GetBooksByLibrary { + libraries { + books { + doesnotexist { + name + } + } + } +} \ No newline at end of file diff --git a/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/variablesInsideFragments.gql b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/variablesInsideFragments.gql new file mode 100644 index 0000000000..17949313a0 --- /dev/null +++ b/instrumentation/graphql-java-21.0/src/test/resources/transactionNameTestData/variablesInsideFragments.gql @@ -0,0 +1,20 @@ +query HeroComparison($first: Int = 3) { + leftComparison: hero(episode: EMPIRE) { + ...comparisonFields + } + rightComparison: hero(episode: JEDI) { + ...comparisonFields + } +} + +fragment comparisonFields on Character { + name + friendsConnection(first: $first) { + totalCount + edges { + node { + name + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8920a4a232..c2283b2471 100644 --- a/settings.gradle +++ b/settings.gradle @@ -103,6 +103,7 @@ include 'instrumentation:grails-2' include 'instrumentation:grails-async-2.3' include 'instrumentation:graphql-java-16.2' include 'instrumentation:graphql-java-17.0' +include 'instrumentation:graphql-java-21.0' include 'instrumentation:grpc-1.4.0' include 'instrumentation:grpc-1.22.0' include 'instrumentation:grpc-1.30.0' From dc3fdce5d9f2808ae43ce0f1cc16a37e4af14e23 Mon Sep 17 00:00:00 2001 From: Koen Punt Date: Thu, 17 Aug 2023 02:08:24 +0200 Subject: [PATCH 2/7] graphql-java-21: configure java language version to fix build --- instrumentation/graphql-java-21.0/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/instrumentation/graphql-java-21.0/build.gradle b/instrumentation/graphql-java-21.0/build.gradle index 941b7ace2c..90d18a7a3a 100644 --- a/instrumentation/graphql-java-21.0/build.gradle +++ b/instrumentation/graphql-java-21.0/build.gradle @@ -15,6 +15,12 @@ jar { manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.graphql-java-21.0' } } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + verifyInstrumentation { passesOnly 'com.graphql-java:graphql-java:[21.0,)' excludeRegex 'com.graphql-java:graphql-java:(0.0.0|201|202).*' From 16e0bb0af3f82aea17452acdc0aa294cd3a97fd6 Mon Sep 17 00:00:00 2001 From: Koen Punt Date: Thu, 17 Aug 2023 10:23:13 +0200 Subject: [PATCH 3/7] graphql-java-21: update instrumentation --- .../main/java/graphql/ExecutionStrategy_Instrumentation.java | 2 +- .../src/test/java/graphql/GraphQL_InstrumentationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java index 78102128cf..818bf6f565 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java @@ -33,7 +33,7 @@ protected CompletableFuture resolveFieldWithInfo(ExecutionContex return Weaver.callOriginal(); } - protected CompletableFuture handleFetchingException(ExecutionContext executionContext, DataFetchingEnvironment environment, Throwable e) { + protected CompletableFuture handleFetchingException(DataFetchingEnvironment environment, Throwable e) { NewRelic.noticeError(e); return Weaver.callOriginal(); } diff --git a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java index c98884a8b8..9cd18fe344 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java @@ -91,7 +91,7 @@ public void parsingException() { //when trace(createRunnable(query)); //then - String expectedErrorMessage = "Invalid Syntax : offending token 'cause' at line 1 column 1"; + String expectedErrorMessage = "Invalid syntax with offending token 'cause' at line 1 column 1"; assertErrorOperation("*", "GraphQL/operation", "graphql.parser.InvalidSyntaxException", expectedErrorMessage, true); } From 97342983fcf9478b094e1e6ade85ccd0885953d3 Mon Sep 17 00:00:00 2001 From: jasonjkeller Date: Tue, 24 Oct 2023 16:11:36 -0700 Subject: [PATCH 4/7] Fix broken error handling and related tests. Code cleanup. --- .../graphql/GraphQLErrorHandler.java | 2 +- .../graphql/GraphQLObfuscator.java | 2 +- .../graphql/GraphQLOperationDefinition.java | 2 +- .../graphql/GraphQLSpanUtil.java | 2 +- .../graphql/GraphQLTransactionName.java | 27 ++++++++++++++----- .../com/nr/instrumentation/graphql/Utils.java | 7 +++++ .../ExecutionStrategy_Instrumentation.java | 6 ++--- .../java/graphql/GraphQL_Instrumentation.java | 2 +- .../ParseAndValidate_Instrumentation.java | 11 +++++--- .../graphql/GraphQLObfuscatorTest.java | 2 +- .../graphql/GraphQLSpanUtilTest.java | 5 ++-- .../graphql/GraphQLTransactionNameTest.java | 2 +- .../graphql/helper/GraphQLTestHelper.java | 2 +- .../graphql/helper/PrivateApiStub.java | 2 +- .../graphql/GraphQL_InstrumentationTest.java | 10 ++++--- 15 files changed, 57 insertions(+), 27 deletions(-) diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java index 24cb7c4352..d742edafbd 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLErrorHandler.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java index cf87b05286..2427c00c51 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLObfuscator.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java index 7e8aa048bc..7eac4fec72 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLOperationDefinition.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java index b63647bcbd..bbd91d9884 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLSpanUtil.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java index 66bbcbccf6..c5152db5e5 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/GraphQLTransactionName.java @@ -1,13 +1,20 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ package com.nr.instrumentation.graphql; -import graphql.language.*; +import graphql.language.Document; +import graphql.language.Field; +import graphql.language.InlineFragment; +import graphql.language.OperationDefinition; +import graphql.language.Selection; +import graphql.language.SelectionSet; +import graphql.language.SelectionSetContainer; +import graphql.language.TypeName; import java.util.ArrayList; import java.util.List; @@ -41,9 +48,13 @@ public class GraphQLTransactionName { * @return a transaction name based on given document */ public static String from(final Document document) { - if (document == null) return DEFAULT_TRANSACTION_NAME; + if (document == null) { + return DEFAULT_TRANSACTION_NAME; + } List operationDefinitions = document.getDefinitionsOfType(OperationDefinition.class); - if (isNullOrEmpty(operationDefinitions)) return DEFAULT_TRANSACTION_NAME; + if (isNullOrEmpty(operationDefinitions)) { + return DEFAULT_TRANSACTION_NAME; + } if (operationDefinitions.size() == 1) { return getTransactionNameFor(operationDefinitions.get(0)); } @@ -53,7 +64,9 @@ public static String from(final Document document) { } private static String getTransactionNameFor(OperationDefinition operationDefinition) { - if (operationDefinition == null) return DEFAULT_TRANSACTION_NAME; + if (operationDefinition == null) { + return DEFAULT_TRANSACTION_NAME; + } return createBeginningOfTransactionNameFrom(operationDefinition) + createEndOfTransactionNameFrom(operationDefinition.getSelectionSet()); } @@ -66,7 +79,9 @@ private static String createBeginningOfTransactionNameFrom(final OperationDefini private static String createEndOfTransactionNameFrom(final SelectionSet selectionSet) { Selection selection = onlyNonFederatedSelectionOrNoneFrom(selectionSet); - if (selection == null) return ""; + if (selection == null) { + return ""; + } List selections = new ArrayList<>(); while (selection != null) { selections.add(selection); diff --git a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java index 9a1747e054..53baafd95f 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java +++ b/instrumentation/graphql-java-21.0/src/main/java/com/nr/instrumentation/graphql/Utils.java @@ -1,3 +1,10 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + package com.nr.instrumentation.graphql; import java.util.Collection; diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java index 818bf6f565..c032033ae4 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ExecutionStrategy_Instrumentation.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ @@ -19,8 +19,8 @@ import java.util.concurrent.CompletableFuture; -import static com.nr.instrumentation.graphql.GraphQLSpanUtil.*; -import static com.nr.instrumentation.graphql.GraphQLErrorHandler.*; +import static com.nr.instrumentation.graphql.GraphQLErrorHandler.reportNonNullableExceptionToNR; +import static com.nr.instrumentation.graphql.GraphQLSpanUtil.setResolverAttributes; @Weave(originalName = "graphql.execution.ExecutionStrategy", type = MatchType.BaseClass) public class ExecutionStrategy_Instrumentation { diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java index 374bd487ec..8d0122b5bd 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/GraphQL_Instrumentation.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java index c616132493..538906c78d 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ @@ -17,9 +17,12 @@ import graphql.validation.ValidationError; import java.util.List; +import java.util.Locale; +import java.util.function.Predicate; -import static com.nr.instrumentation.graphql.GraphQLSpanUtil.*; -import static com.nr.instrumentation.graphql.GraphQLErrorHandler.*; +import static com.nr.instrumentation.graphql.GraphQLErrorHandler.reportGraphQLError; +import static com.nr.instrumentation.graphql.GraphQLErrorHandler.reportGraphQLException; +import static com.nr.instrumentation.graphql.GraphQLSpanUtil.setOperationAttributes; @Weave(originalName = "graphql.ParseAndValidate", type = MatchType.ExactClass) public class ParseAndValidate_Instrumentation { @@ -41,7 +44,7 @@ public static ParseAndValidateResult parse(ExecutionInput executionInput) { return result; } - public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument) { + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate, Locale locale) { List errors = Weaver.callOriginal(); if (errors != null && !errors.isEmpty()) { reportGraphQLError(errors.get(0)); diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java index 003baf1050..dd7cc6347d 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java index f61178b9c0..71ac42cd4c 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ @@ -111,7 +111,8 @@ public void testInvalidClassExceptionFix() { "'graphql.field.parentType' is not required and should be null here"); } - private static ExecutionStrategyParameters mockExecutionStrategyParametersForInvalidClassCastExceptionTest(String graphqlFieldPath, String graphqlFieldName) { + private static ExecutionStrategyParameters mockExecutionStrategyParametersForInvalidClassCastExceptionTest(String graphqlFieldPath, + String graphqlFieldName) { ExecutionStrategyParameters parameters = mock(ExecutionStrategyParameters.class); ResultPath resultPath = mock(ResultPath.class); MergedField mergedField = mock(MergedField.class); diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java index ba130cc1a2..3d2fdc63d0 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java index 9da9fdeed7..15b90b49cd 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java index 971076d338..2316e892c0 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ diff --git a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java index 9cd18fe344..1f7d7a36e4 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright 2020 New Relic Corporation. All rights reserved. + * * Copyright 2023 New Relic Corporation. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ @@ -12,6 +12,7 @@ import com.newrelic.agent.introspec.Introspector; import com.newrelic.agent.introspec.SpanEvent; import com.newrelic.api.agent.Trace; +import com.newrelic.test.marker.Java8IncompatibleTest; import graphql.schema.GraphQLSchema; import graphql.schema.StaticDataFetcher; import graphql.schema.idl.RuntimeWiring; @@ -21,6 +22,7 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.jupiter.api.AfterEach; import org.junit.runner.RunWith; @@ -34,7 +36,8 @@ import static org.junit.Assert.assertTrue; @RunWith(InstrumentationTestRunner.class) -@InstrumentationTestConfig(includePrefixes = {"graphql", "com.nr.instrumentation"}, configName = "distributed_tracing.yml") +@InstrumentationTestConfig(includePrefixes = { "graphql", "com.nr.instrumentation" }, configName = "distributed_tracing.yml") +@Category({ Java8IncompatibleTest.class }) public class GraphQL_InstrumentationTest { private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10_000; private static final String TEST_ARG = "testArg"; @@ -103,7 +106,8 @@ public void validationException() { //when trace(createRunnable(query)); //then - String expectedErrorMessage = "Validation error of type FieldUndefined: Field 'noSuchField' in type 'Query' is undefined @ 'noSuchField'"; +// String expectedErrorMessage = "Validation error of type FieldUndefined: Field 'noSuchField' in type 'Query' is undefined @ 'noSuchField'"; + String expectedErrorMessage = "Validation error (FieldUndefined@[noSuchField]) : Field 'noSuchField' in type 'Query' is undefined"; assertErrorOperation("QUERY//noSuchField", "GraphQL/operation/QUERY//noSuchField", "graphql.GraphqlErrorException", expectedErrorMessage, false); } From 27d57b7128f3e1c7d6af6dca02408374907cf86b Mon Sep 17 00:00:00 2001 From: jasonjkeller Date: Tue, 24 Oct 2023 16:12:54 -0700 Subject: [PATCH 5/7] Remove commented out code --- .../src/test/java/graphql/GraphQL_InstrumentationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java index 1f7d7a36e4..bca3c401bd 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/graphql/GraphQL_InstrumentationTest.java @@ -106,7 +106,6 @@ public void validationException() { //when trace(createRunnable(query)); //then -// String expectedErrorMessage = "Validation error of type FieldUndefined: Field 'noSuchField' in type 'Query' is undefined @ 'noSuchField'"; String expectedErrorMessage = "Validation error (FieldUndefined@[noSuchField]) : Field 'noSuchField' in type 'Query' is undefined"; assertErrorOperation("QUERY//noSuchField", "GraphQL/operation/QUERY//noSuchField", "graphql.GraphqlErrorException", expectedErrorMessage, false); From d4ef30fe1cab6feb546c02847adde00f41c19e0e Mon Sep 17 00:00:00 2001 From: jasonjkeller Date: Wed, 25 Oct 2023 10:21:30 -0700 Subject: [PATCH 6/7] Prevent instrumentation tests from running on Java 8 --- instrumentation/graphql-java-21.0/build.gradle | 6 ++++++ .../nr/instrumentation/graphql/GraphQLObfuscatorTest.java | 3 +++ .../com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java | 3 +++ .../instrumentation/graphql/GraphQLTransactionNameTest.java | 3 +++ .../instrumentation/graphql/helper/GraphQLTestHelper.java | 3 +++ .../nr/instrumentation/graphql/helper/PrivateApiStub.java | 3 +++ 6 files changed, 21 insertions(+) diff --git a/instrumentation/graphql-java-21.0/build.gradle b/instrumentation/graphql-java-21.0/build.gradle index 90d18a7a3a..87e4fff5cf 100644 --- a/instrumentation/graphql-java-21.0/build.gradle +++ b/instrumentation/graphql-java-21.0/build.gradle @@ -34,4 +34,10 @@ site { test { useJUnitPlatform() + // These instrumentation tests only run on Java 11+ regardless of the -PtestN gradle property that is set. + onlyIf { + !project.hasProperty('test8') + } + } + diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java index dd7cc6347d..da5a060476 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLObfuscatorTest.java @@ -7,12 +7,15 @@ package com.nr.instrumentation.graphql; +import com.newrelic.test.marker.Java8IncompatibleTest; +import org.junit.experimental.categories.Category; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileSource; import static com.nr.instrumentation.graphql.helper.GraphQLTestHelper.readText; import static org.junit.jupiter.api.Assertions.assertEquals; +@Category({ Java8IncompatibleTest.class }) public class GraphQLObfuscatorTest { private final static String OBFUSCATE_DATA_DIR = "obfuscateQueryTestData"; diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java index 71ac42cd4c..56cd588770 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLSpanUtilTest.java @@ -9,6 +9,7 @@ import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.agent.bridge.PrivateApi; +import com.newrelic.test.marker.Java8IncompatibleTest; import com.nr.instrumentation.graphql.helper.GraphQLTestHelper; import com.nr.instrumentation.graphql.helper.PrivateApiStub; import graphql.execution.ExecutionStepInfo; @@ -18,6 +19,7 @@ import graphql.language.Definition; import graphql.language.Document; import graphql.schema.GraphQLNonNull; +import org.junit.experimental.categories.Category; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@Category({ Java8IncompatibleTest.class }) public class GraphQLSpanUtilTest { private static final List NO_DEFINITIONS = Collections.emptyList(); diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java index 3d2fdc63d0..2080fe3671 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/GraphQLTransactionNameTest.java @@ -7,13 +7,16 @@ package com.nr.instrumentation.graphql; +import com.newrelic.test.marker.Java8IncompatibleTest; import graphql.language.Document; +import org.junit.experimental.categories.Category; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileSource; import static com.nr.instrumentation.graphql.helper.GraphQLTestHelper.parseDocument; import static org.junit.jupiter.api.Assertions.assertEquals; +@Category({ Java8IncompatibleTest.class }) public class GraphQLTransactionNameTest { private final static String TEST_DATA_DIR = "transactionNameTestData"; diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java index 15b90b49cd..78a17f663e 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/GraphQLTestHelper.java @@ -7,13 +7,16 @@ package com.nr.instrumentation.graphql.helper; +import com.newrelic.test.marker.Java8IncompatibleTest; import graphql.language.Document; import graphql.parser.Parser; +import org.junit.experimental.categories.Category; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +@Category({ Java8IncompatibleTest.class }) public class GraphQLTestHelper { public static Document parseDocument(String testDir, String filename) { return Parser.parse(readText(testDir, filename)); diff --git a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java index 2316e892c0..f5ed2da690 100644 --- a/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java +++ b/instrumentation/graphql-java-21.0/src/test/java/com/nr/instrumentation/graphql/helper/PrivateApiStub.java @@ -8,6 +8,8 @@ package com.nr.instrumentation.graphql.helper; import com.newrelic.agent.bridge.PrivateApi; +import com.newrelic.test.marker.Java8IncompatibleTest; +import org.junit.experimental.categories.Category; import javax.management.MBeanServer; import java.io.Closeable; @@ -15,6 +17,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +@Category({ Java8IncompatibleTest.class }) public class PrivateApiStub implements PrivateApi { private final Map tracerParameters = new HashMap<>(); From 8f3af9b8bae4f5f08de1519577968413929031a4 Mon Sep 17 00:00:00 2001 From: jasonjkeller Date: Wed, 25 Oct 2023 12:01:39 -0700 Subject: [PATCH 7/7] Include additional validate signature --- .../java/graphql/ParseAndValidate_Instrumentation.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java index 538906c78d..a19bc1cd93 100644 --- a/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java +++ b/instrumentation/graphql-java-21.0/src/main/java/graphql/ParseAndValidate_Instrumentation.java @@ -51,4 +51,12 @@ public static List validate(GraphQLSchema graphQLSchema, Docume } return errors; } + + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate) { + List errors = Weaver.callOriginal(); + if (errors != null && !errors.isEmpty()) { + reportGraphQLError(errors.get(0)); + } + return errors; + } }