Skip to content

Commit

Permalink
Merge pull request #1565 from newrelic/pr-1454
Browse files Browse the repository at this point in the history
Pr 1454
  • Loading branch information
jasonjkeller committed Oct 26, 2023
2 parents 1c4eef2 + 8f3af9b commit 3759a9a
Show file tree
Hide file tree
Showing 45 changed files with 1,479 additions and 0 deletions.
43 changes: 43 additions & 0 deletions instrumentation/graphql-java-21.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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' }
}

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).*'
excludeRegex 'com.graphql-java:graphql-java:.*(vTEST|-beta|-alpha1|-nf-execution|-rc|-TEST).*'
}

site {
title 'GraphQL Java'
type 'Framework'
}

test {
useJUnitPlatform()
// These instrumentation tests only run on Java 11+ regardless of the -PtestN gradle property that is set.
onlyIf {
!project.hasProperty('test8')
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
*
* * Copyright 2023 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<ExecutionResult> 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<ExecutionResult> exceptionResult) {
return exceptionResult != null && exceptionResult.isCompletedExceptionally();
}

private static void reportExceptionFromCompletedExceptionally(CompletableFuture<ExecutionResult> 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
*
* * Copyright 2023 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;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
*
* * Copyright 2023 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 = "<anonymous>";
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<OperationDefinition> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
*
* * Copyright 2023 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 = "<anonymous>";

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));
}
}
Loading

0 comments on commit 3759a9a

Please sign in to comment.