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
Prev Previous commit
Next Next commit
Fix instrumentation:
- do not finish parent span
- handle exceptions thrown by data fetchers
- set parent span status if any of child spans fails
maciejwalkowiak committed Nov 2, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 542c6b6eceb5d0b597d20f6c3e3fb0927625dbf6
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.graphql;

import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
@@ -13,7 +14,9 @@
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.SpanStatus;
import io.sentry.util.Objects;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -57,17 +60,28 @@ public SentryInstrumentation() {
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();
try {
final Object result = dataFetcher.get(environment);
if (result instanceof CompletableFuture) {
((CompletableFuture<?>) result)
.whenComplete(
(r, ex) -> {
if (ex != null) {
span.setThrowable(ex);
span.finish(SpanStatus.INTERNAL_ERROR);
} else {
span.finish();
}
});
} else {
span.finish();
}
return result;
} catch (Exception e) {
span.setThrowable(e);
span.finish(SpanStatus.INTERNAL_ERROR);
throw e;
}
return result;
} else {
return dataFetcher.get(environment);
}
@@ -81,8 +95,9 @@ public SentryInstrumentation() {
TracingState tracingState = parameters.getInstrumentationState();
final ISpan transaction = tracingState.getTransaction();

if (transaction != null) {
transaction.finish();
final List<GraphQLError> errors = executionResult.getErrors();
if (transaction != null && errors != null && !errors.isEmpty()) {
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
}

return super.instrumentExecutionResult(executionResult, parameters);
Original file line number Diff line number Diff line change
@@ -11,17 +11,19 @@ import graphql.schema.idl.SchemaGenerator
import graphql.schema.idl.SchemaParser
import io.sentry.IHub
import io.sentry.ISpan
import io.sentry.SpanStatus
import java.lang.RuntimeException
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertTrue

class SentryInstrumentationTest {

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

fun getSut(isTransactionActive: Boolean = true): GraphQL {
fun getSut(isTransactionActive: Boolean = true, dataFetcherThrows: Boolean = false): GraphQL {
val schema = """
type Query {
shows: [Show]
@@ -33,25 +35,29 @@ class SentryInstrumentationTest {
""".trimIndent()
val hub = mock<IHub>()

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

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

return graphQL
}

private fun buildRuntimeWiring() = RuntimeWiring.newRuntimeWiring()
private fun buildRuntimeWiring(dataFetcherThrows: Boolean) = RuntimeWiring.newRuntimeWiring()
.type("Query") {
it.dataFetcher("shows") {
listOf(Show(Random.nextInt()), Show(Random.nextInt()))
if (dataFetcherThrows) {
throw RuntimeException("error")
} else {
listOf(Show(Random.nextInt()), Show(Random.nextInt()))
}
}
}.build()
}
@@ -65,19 +71,32 @@ class SentryInstrumentationTest {
val result = sut.execute("{ shows { id } }")

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

@Test
fun `when transaction is not , does not create spans`() {
fun `when transaction is active, and data fetcher throws, creates inner spans`() {
val sut = fixture.getSut(dataFetcherThrows = true)

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

assertTrue(result.errors.isNotEmpty())
verify(fixture.activeSpan).startChild("Query.shows")
verify(fixture.innerSpan).finish(SpanStatus.INTERNAL_ERROR)
verify(fixture.activeSpan).status = SpanStatus.INTERNAL_ERROR
verifyNoMoreInteractions(fixture.activeSpan)
}

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

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

assertTrue(result.errors.isEmpty())
verifyNoMoreInteractions(fixture.transaction)
verifyNoMoreInteractions(fixture.activeSpan)
verifyNoMoreInteractions(fixture.innerSpan)
}

Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import com.netflix.graphql.dgs.DgsQuery;
import io.sentry.samples.netflix.dgs.graphql.DgsConstants;
import io.sentry.samples.netflix.dgs.graphql.types.Show;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

@@ -13,11 +14,14 @@ public class ShowsDatafetcher {
@DgsQuery(field = DgsConstants.QUERY.Shows)
public List<Show> shows() throws InterruptedException {
Thread.sleep(new Random().nextInt(500));
return Arrays.asList(
Show.newBuilder().id(1).title("Stranger Things").releaseYear(2015).build(),
Show.newBuilder().id(2).title("Breaking Bad").releaseYear(2013).build());
}

// return Arrays.asList(
// Show.newBuilder().id(1).title("Stranger Things").releaseYear(2015).build(),
// Show.newBuilder().id(2).title("Breaking Bad").releaseYear(2013).build()
// );
throw new RuntimeException("foo");
@DgsQuery(field = DgsConstants.QUERY.NewShows)
public List<Show> newShows() throws InterruptedException {
Thread.sleep(new Random().nextInt(500));
throw new RuntimeException("error when loading new shows");
}
}
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ public static class QUERY {
public static final String TYPE_NAME = "Query";

public static final String Shows = "shows";

public static final String NewShows = "newShows";
}

public static class SHOW {
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type Query {
shows: [Show]
newShows: [Show]
}

type Show {