Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pr 1454 #1565

Merged
merged 7 commits into from
Oct 26, 2023
Merged

Pr 1454 #1565

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading