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

Feat: Add graphql-java instrumentation. #1777

Merged
merged 25 commits into from
Nov 8, 2021
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c1f11cb
Feat: Add `graphql-java` instrumentation.
maciejwalkowiak Oct 22, 2021
5e454a7
Changelog.
maciejwalkowiak Oct 22, 2021
f0f9027
Merge remote-tracking branch 'origin/main' into gh-1755
maciejwalkowiak Oct 25, 2021
3d27109
Reformat.
maciejwalkowiak Oct 25, 2021
e861e57
Add graphql exception handler.
maciejwalkowiak Oct 25, 2021
a43603b
Add Netflix DGS sample
maciejwalkowiak Oct 25, 2021
67265a2
Add missing files.
maciejwalkowiak Oct 25, 2021
4ef521d
Polish.
maciejwalkowiak Oct 28, 2021
ef4b780
Remove `java` package.
maciejwalkowiak Oct 28, 2021
60474bc
Polish.
maciejwalkowiak Oct 28, 2021
9245a43
Merge remote-tracking branch 'origin/main' into gh-1755
maciejwalkowiak Oct 29, 2021
f16a681
Merge branch 'main' into gh-1755
bruno-garcia Nov 2, 2021
a66aeed
Merge remote-tracking branch 'origin/main' into gh-1755
maciejwalkowiak Nov 2, 2021
e37ddd9
Merge branch 'gh-1755' of https://github.com/getsentry/sentry-java in…
maciejwalkowiak Nov 2, 2021
de5d9bd
Remove API check from Netflix DGS sample.
maciejwalkowiak Nov 2, 2021
542c6b6
Fix instrumentation:
maciejwalkowiak Nov 2, 2021
8180ec2
Update sentry-graphql-java/src/main/java/io/sentry/graphql/SentryInst…
maciejwalkowiak Nov 3, 2021
8ec6a1f
Update sentry-graphql-java/src/main/java/io/sentry/graphql/SentryInst…
maciejwalkowiak Nov 3, 2021
96c8e30
Pass parameters as a hint
maciejwalkowiak Nov 3, 2021
cabfa4a
Add BeforeSpan to SentryInstrumentation.
maciejwalkowiak Nov 3, 2021
b0fc86d
Merge branch 'gh-1755' of https://github.com/getsentry/sentry-java in…
maciejwalkowiak Nov 3, 2021
6c77fca
Add craft.
maciejwalkowiak Nov 3, 2021
9c55ee8
Do not set status on parent span.
maciejwalkowiak Nov 8, 2021
f42ef1f
Polish.
maciejwalkowiak Nov 8, 2021
10258a2
Set span status.
maciejwalkowiak Nov 8, 2021
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
Next Next commit
Feat: Add graphql-java instrumentation.
Fixes #1755
maciejwalkowiak committed Oct 22, 2021
commit c1f11cbfe6e3aa6b5274697d478a44c5ba7f2396
2 changes: 2 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
@@ -95,6 +95,8 @@ object Config {
private val apolloVersion = "2.5.9"
val apolloAndroid = "com.apollographql.apollo:apollo-runtime:$apolloVersion"
val apolloCoroutines = "com.apollographql.apollo:apollo-coroutines-support:$apolloVersion"

val graphQlJava = "com.graphql-java:graphql-java:17.3"
}

object AnnotationProcessors {
9 changes: 9 additions & 0 deletions sentry-graphql-java/api/sentry-graphql-java.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public final class io/sentry/graphql/java/SentryInstrumentation : graphql/execution/instrumentation/SimpleInstrumentation {
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;)V
public fun beginExecution (Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;)Lgraphql/execution/instrumentation/InstrumentationContext;
public fun createState ()Lgraphql/execution/instrumentation/InstrumentationState;
public fun instrumentDataFetcher (Lgraphql/schema/DataFetcher;Lgraphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters;)Lgraphql/schema/DataFetcher;
public fun instrumentExecutionResult (Lgraphql/ExecutionResult;Lgraphql/execution/instrumentation/parameters/InstrumentationExecutionParameters;)Ljava/util/concurrent/CompletableFuture;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.sentry.graphql.java;

import graphql.ExecutionResult;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class SentryInstrumentation extends SimpleInstrumentation {
private final @NotNull IHub hub;

public SentryInstrumentation(final @NotNull IHub hub) {
this.hub = Objects.requireNonNull(hub, "hub is required");
}

public SentryInstrumentation() {
this(HubAdapter.getInstance());
}

@Override
public @NotNull InstrumentationState createState() {
return new TracingState();
}

@Override
public @NotNull InstrumentationContext<ExecutionResult> beginExecution(
final @NotNull InstrumentationExecutionParameters parameters) {
TracingState tracingState = parameters.getInstrumentationState();
tracingState.setTransaction(hub.getSpan());
return super.beginExecution(parameters);
}

@Override
@SuppressWarnings("FutureReturnValueIgnored")
public @NotNull DataFetcher<?> instrumentDataFetcher(
final @NotNull DataFetcher<?> dataFetcher,
final @NotNull InstrumentationFieldFetchParameters parameters) {
// We only care about user code
if (parameters.isTrivialDataFetcher()) {
return dataFetcher;
}

return environment -> {
final TracingState tracingState = parameters.getInstrumentationState();
final ISpan transaction = tracingState.getTransaction();
if (transaction != null) {
final ISpan span = transaction.startChild(findDataFetcherTag(parameters));
final Object result = dataFetcher.get(environment);
if (result instanceof CompletableFuture) {
((CompletableFuture<?>) result)
.whenComplete(
(r, ex) -> {
span.finish();
});
} else {
span.finish();
}
return result;
} else {
return dataFetcher.get(environment);
}
};
}

@Override
public @NotNull CompletableFuture<ExecutionResult> instrumentExecutionResult(
final @NotNull ExecutionResult executionResult,
final @NotNull InstrumentationExecutionParameters parameters) {
TracingState tracingState = parameters.getInstrumentationState();
final ISpan transaction = tracingState.getTransaction();

if (transaction != null) {
transaction.finish();
}

return super.instrumentExecutionResult(executionResult, parameters);
}

private @NotNull String findDataFetcherTag(
final @NotNull InstrumentationFieldFetchParameters parameters) {
final GraphQLOutputType type = parameters.getExecutionStepInfo().getParent().getType();
GraphQLObjectType parent;
if (type instanceof GraphQLNonNull) {
parent = (GraphQLObjectType) ((GraphQLNonNull) type).getWrappedType();
} else {
parent = (GraphQLObjectType) type;
}

return parent.getName() + "." + parameters.getExecutionStepInfo().getPath().getSegmentName();
}

static final class TracingState implements InstrumentationState {
private @Nullable ISpan transaction;

public @Nullable ISpan getTransaction() {
return transaction;
}

public void setTransaction(final @Nullable ISpan transaction) {
this.transaction = transaction;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.sentry.graphql.java

import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import graphql.GraphQL
import graphql.schema.idl.RuntimeWiring
import graphql.schema.idl.SchemaGenerator
import graphql.schema.idl.SchemaParser
import io.sentry.IHub
import io.sentry.ISpan
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertTrue

class SentryInstrumentationTest {

class Fixture {
val transaction = mock<ISpan>()
val innerSpan = mock<ISpan>()

fun getSut(isTransactionActive: Boolean = true): GraphQL {
val schema = """
type Query {
shows: [Show]
}

type Show {
id: Int
}
""".trimIndent()
val hub = mock<IHub>()

val graphQLSchema = SchemaGenerator().makeExecutableSchema(SchemaParser().parse(schema), buildRuntimeWiring())
val graphQL = GraphQL.newGraphQL(graphQLSchema)
.instrumentation(SentryInstrumentation(hub))
.build()

if (isTransactionActive) {
whenever(hub.span).thenReturn(transaction)
} else {
whenever(hub.span).thenReturn(null)
}
whenever(transaction.startChild(any())).thenReturn(innerSpan)

return graphQL
}

private fun buildRuntimeWiring() = RuntimeWiring.newRuntimeWiring()
.type("Query") {
it.dataFetcher("shows") {
listOf(Show(Random.nextInt()), Show(Random.nextInt()))
}
}.build()
}

private val fixture = Fixture()

@Test
fun `when transaction is active, creates inner spans`() {
val sut = fixture.getSut()

val result = sut.execute("{ shows { id } }")

assertTrue(result.errors.isEmpty())
verify(fixture.transaction).startChild("Query.shows")
verify(fixture.innerSpan).finish()
verify(fixture.transaction).finish()
}

@Test
fun `when transaction is not , does not create spans`() {
val sut = fixture.getSut(isTransactionActive = false)

val result = sut.execute("{ shows { id } }")

assertTrue(result.errors.isEmpty())
verifyZeroInteractions(fixture.transaction)
verifyZeroInteractions(fixture.innerSpan)
}

data class Show(val id: Int)
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ include(
"sentry-spring-boot-starter",
"sentry-bom",
"sentry-openfeign",
"sentry-graphql-java",
"sentry-samples:sentry-samples-android",
"sentry-samples:sentry-samples-console",
"sentry-samples:sentry-samples-jul",