Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ public static GraphQLTelemetryBuilder builder(OpenTelemetry openTelemetry) {
}

private final OpenTelemetryInstrumentationHelper helper;
private final Instrumenter<DataFetchingEnvironment, Void> dataFetcherInstrumenter;
private final Instrumenter<DataFetchingEnvironment, Object> dataFetcherInstrumenter;
private final boolean createSpansForTrivialDataFetcher;

GraphQLTelemetry(
OpenTelemetry openTelemetry,
boolean sanitizeQuery,
Instrumenter<DataFetchingEnvironment, Void> dataFetcherInstrumenter,
Instrumenter<DataFetchingEnvironment, Object> dataFetcherInstrumenter,
boolean createSpansForTrivialDataFetcher,
boolean addOperationNameToSpanName) {
helper =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import javax.annotation.Nullable;

final class GraphqlDataFetcherAttributesExtractor
implements AttributesExtractor<DataFetchingEnvironment, Void> {
implements AttributesExtractor<DataFetchingEnvironment, Object> {

// NOTE: These are not part of the Semantic Convention and are subject to change
private static final AttributeKey<String> GRAPHQL_FIELD_NAME =
Expand All @@ -34,6 +34,6 @@ public void onEnd(
AttributesBuilder attributes,
Context context,
DataFetchingEnvironment environment,
@Nullable Void unused,
@Nullable Object unused,
@Nullable Throwable error) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

package io.opentelemetry.instrumentation.graphql.v20_0;

import graphql.execution.DataFetcherResult;
import graphql.schema.DataFetchingEnvironment;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;

final class GraphqlInstrumenterFactory {
Expand All @@ -20,13 +23,23 @@ static OpenTelemetryInstrumentationHelper createInstrumentationHelper(
openTelemetry, INSTRUMENTATION_NAME, sanitizeQuery, addOperationNameToSpanName);
}

static Instrumenter<DataFetchingEnvironment, Void> createDataFetcherInstrumenter(
static Instrumenter<DataFetchingEnvironment, Object> createDataFetcherInstrumenter(
OpenTelemetry openTelemetry, boolean enabled) {
return Instrumenter.<DataFetchingEnvironment, Void>builder(
return Instrumenter.<DataFetchingEnvironment, Object>builder(
openTelemetry,
INSTRUMENTATION_NAME,
environment -> environment.getExecutionStepInfo().getField().getName())
.addAttributesExtractor(new GraphqlDataFetcherAttributesExtractor())
.setSpanStatusExtractor(
(spanStatusBuilder, dataFetchingEnvironment, result, error) -> {
if (result instanceof DataFetcherResult
&& ((DataFetcherResult<?>) result).hasErrors()) {
spanStatusBuilder.setStatus(StatusCode.ERROR);
} else {
SpanStatusExtractor.getDefault()
.extract(spanStatusBuilder, dataFetchingEnvironment, result, error);
}
})
.setEnabled(enabled)
.buildInstrumenter();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static graphql.execution.instrumentation.InstrumentationState.ofState;

import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.execution.DataFetcherResult;
import graphql.execution.ResultPath;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
Expand All @@ -18,21 +20,25 @@
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationState;
import io.opentelemetry.semconv.ExceptionAttributes;
import java.util.concurrent.CompletionStage;

final class OpenTelemetryInstrumentation extends SimplePerformantInstrumentation {
private final OpenTelemetryInstrumentationHelper helper;
private final Instrumenter<DataFetchingEnvironment, Void> dataFetcherInstrumenter;
private final Instrumenter<DataFetchingEnvironment, Object> dataFetcherInstrumenter;
private final boolean createSpansForTrivialDataFetcher;

OpenTelemetryInstrumentation(
OpenTelemetryInstrumentationHelper helper,
Instrumenter<DataFetchingEnvironment, Void> dataFetcherInstrumenter,
Instrumenter<DataFetchingEnvironment, Object> dataFetcherInstrumenter,
boolean createSpansForTrivialDataFetcher) {
this.helper = helper;
this.dataFetcherInstrumenter = dataFetcherInstrumenter;
Expand Down Expand Up @@ -84,28 +90,45 @@ public DataFetcher<?> instrumentDataFetcher(

boolean isCompletionStage = false;

Object fieldValue = null;
try (Scope ignored = childContext.makeCurrent()) {
Object fieldValue = dataFetcher.get(environment);

fieldValue = dataFetcher.get(environment);
isCompletionStage = fieldValue instanceof CompletionStage;

if (isCompletionStage) {
return ((CompletionStage<?>) fieldValue)
.whenComplete(
(result, throwable) ->
dataFetcherInstrumenter.end(childContext, environment, null, throwable));
(result, throwable) -> {
handleDataFetcherResult(childContext, result);
dataFetcherInstrumenter.end(childContext, environment, result, throwable);
});
}

return fieldValue;

} catch (Throwable throwable) {
dataFetcherInstrumenter.end(childContext, environment, null, throwable);
throw throwable;
} finally {
if (!isCompletionStage) {
dataFetcherInstrumenter.end(childContext, environment, null, null);
handleDataFetcherResult(childContext, fieldValue);
dataFetcherInstrumenter.end(childContext, environment, fieldValue, null);
}
}
};
}

private static void handleDataFetcherResult(Context context, Object result) {
if (!(result instanceof DataFetcherResult)) {
return;
}

DataFetcherResult<?> dataFetcherResult = (DataFetcherResult<?>) result;
Span span = Span.fromContext(context);
for (GraphQLError error : dataFetcherResult.getErrors()) {
AttributesBuilder attributes = Attributes.builder();
attributes.put(ExceptionAttributes.EXCEPTION_TYPE, String.valueOf(error.getErrorType()));
attributes.put(ExceptionAttributes.EXCEPTION_MESSAGE, error.getMessage());

span.addEvent("exception", attributes.build());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.ExceptionAttributes;
import io.opentelemetry.semconv.incubating.GraphqlIncubatingAttributes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand Down Expand Up @@ -239,6 +241,75 @@ void noDataFetcherSpansCreated() {
.hasParent(spanWithName("query findBookById"))));
}

@Test
void dataFetcherError() {
// Arrange
GraphQLTelemetry telemetry =
GraphQLTelemetry.builder(testing.getOpenTelemetry())
.setDataFetcherInstrumentationEnabled(true)
.setAddOperationNameToSpanName(true)
.build();

GraphQL graphql =
GraphQL.newGraphQL(graphqlSchema).instrumentation(telemetry.newInstrumentation()).build();

// Act
ExecutionResult result =
graphql.execute(
""
+ " query findBookById {\n"
+ " bookById(id: \"book-failure\") {\n"
+ " name\n"
+ " author {\n"
+ " name\n"
+ " }\n"
+ " }\n"
+ " }");

// Assert
assertThat(result.getErrors()).isNotEmpty();

testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("query findBookById")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
GraphqlIncubatingAttributes.GRAPHQL_OPERATION_NAME, "findBookById"),
equalTo(GraphqlIncubatingAttributes.GRAPHQL_OPERATION_TYPE, "query"),
normalizedQueryEqualsTo(
GraphqlIncubatingAttributes.GRAPHQL_DOCUMENT,
"query findBookById { bookById(id: ?) { name author { name } } }"))
.hasStatus(StatusData.error())
.hasEventsSatisfyingExactly(
event ->
event
.hasName("exception")
.hasAttributesSatisfyingExactly(
equalTo(
ExceptionAttributes.EXCEPTION_TYPE,
"DataFetchingException"),
equalTo(
ExceptionAttributes.EXCEPTION_MESSAGE,
"Exception while fetching data (/bookById) : fetching book failed"))),
span ->
span.hasName("bookById")
.hasKind(SpanKind.INTERNAL)
.hasParent(spanWithName("query findBookById"))
.hasAttributesSatisfyingExactly(
equalTo(GRAPHQL_FIELD_NAME, "bookById"),
equalTo(GRAPHQL_FIELD_PATH, "/bookById")),
span ->
span.hasName("fetchBookById")
.hasKind(SpanKind.INTERNAL)
.hasParent(spanWithName("bookById"))
.hasStatus(StatusData.error())
.hasException(new IllegalStateException("fetching book failed"))));
}

private static SpanData spanWithName(String name) {
return testing.spans().stream()
.filter(span -> span.getName().equals(name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ private DataFetcher<Map<String, String>> getBookByIdDataFetcher() {
"fetchBookById",
() -> {
String bookId = dataFetchingEnvironment.getArgument("id");
if ("book-failure".equals(bookId)) {
throw new IllegalStateException("fetching book failed");
}
return books.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
Expand Down
Loading