Add instrumentation for graphql 20 that does not use deprecated methods (#10779)

This commit is contained in:
Lauri Tulmin 2024-03-12 13:48:17 +02:00 committed by GitHub
parent 9db9661bc9
commit a3a572540e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 656 additions and 163 deletions

View File

@ -34,17 +34,25 @@ extra["testLatestDeps"] = testLatestDeps
abstract class TestLatestDepsRule : ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
val version = context.details.id.version
if (version.contains("-alpha", true) ||
version.contains("-beta", true) ||
version.contains("-rc", true) ||
version.contains("-m", true) || // e.g. spring milestones are published to grails repo
version.contains(".alpha", true) || // e.g. netty
version.contains(".beta", true) || // e.g. hibernate
version.contains(".cr", true) // e.g. hibernate
if (version.contains("-alpha", true)
|| version.contains("-beta", true)
|| version.contains("-rc", true)
|| version.contains("-m", true) // e.g. spring milestones are published to grails repo
|| version.contains(".alpha", true) // e.g. netty
|| version.contains(".beta", true) // e.g. hibernate
|| version.contains(".cr", true) // e.g. hibernate
|| version.endsWith("-nf-execution") // graphql
|| GIT_SHA_PATTERN.matches(version) // graphql
|| DATETIME_PATTERN.matches(version) // graphql
) {
context.details.status = "milestone"
}
}
companion object {
private val GIT_SHA_PATTERN = Regex("^.*-[0-9a-f]{7,}$")
private val DATETIME_PATTERN = Regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}.*$")
}
}
configurations {

View File

@ -64,7 +64,7 @@ These are the supported libraries and frameworks:
| [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans] |
| [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [Grails](https://grails.org/) | 3.0+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [GraphQL Java](https://www.graphql-java.com/) | 12.0+ | [opentelemetry-graphql-java-12.0](../instrumentation/graphql-java-12.0/library) | [GraphQL Server Spans] |
| [GraphQL Java](https://www.graphql-java.com/) | 12.0+ | [opentelemetry-graphql-java-12.0](../instrumentation/graphql-java/graphql-java-12.0/library),<br>[opentelemetry-graphql-java-20.0](../instrumentation/graphql-java/graphql-java-20.0/library) | [GraphQL Server Spans] |
| [gRPC](https://github.com/grpc/grpc-java) | 1.6+ | [opentelemetry-grpc-1.6](../instrumentation/grpc-1.6/library) | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] |
| [Guava ListenableFuture](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/ListenableFuture.html) | 10.0+ | [opentelemetry-guava-10.0](../instrumentation/guava-10.0/library) | Context propagation |
| [GWT](http://www.gwtproject.org/) | 2.0+ | N/A | [RPC Server Spans] |
@ -76,7 +76,7 @@ These are the supported libraries and frameworks:
| [Java Executors](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) | Java 8+ | N/A | Context propagation |
| [Java Http Client](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html) | Java 11+ | [opentelemetry-java-http-client](../instrumentation/java-http-client/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) | Java 8+ | N/A | none |
| [Java Platform](https://docs.oracle.com/javase/8/docs/api/java/lang/management/ManagementFactory.html) | Java 8+ | [opentelemetry-runtime-telemetry-java8](../instrumentation/runtime-telemetry/runtime-telemetry-java8/library),[opentelemetry-runtime-telemetry-java17](../instrumentation/runtime-telemetry/runtime-telemetry-java17/library),<br>[opentelemetry-resources](../instrumentation/resources/library) | [JVM Runtime Metrics] |
| [Java Platform](https://docs.oracle.com/javase/8/docs/api/java/lang/management/ManagementFactory.html) | Java 8+ | [opentelemetry-runtime-telemetry-java8](../instrumentation/runtime-telemetry/runtime-telemetry-java8/library),<br>[opentelemetry-runtime-telemetry-java17](../instrumentation/runtime-telemetry/runtime-telemetry-java17/library),<br>[opentelemetry-resources](../instrumentation/resources/library) | [JVM Runtime Metrics] |
| [JAX-RS](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/package-summary.html) | 0.5+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [JAX-RS Client](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/client/package-summary.html) | 1.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [JAX-WS](https://jakarta.ee/specifications/xml-web-services/2.3/apidocs/javax/xml/ws/package-summary.html) | 2.0+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] |

View File

@ -1,21 +0,0 @@
plugins {
id("otel.javaagent-instrumentation")
}
muzzle {
pass {
group.set("com.graphql-java")
module.set("graphql-java")
versions.set("[12,)")
skip("230521-nf-execution")
assertInverse.set(true)
}
}
dependencies {
implementation(project(":instrumentation:graphql-java-12.0:library"))
library("com.graphql-java:graphql-java:12.0")
testImplementation(project(":instrumentation:graphql-java-12.0:testing"))
}

View File

@ -1,65 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
import graphql.ExecutionResult;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
@SuppressWarnings("AbbreviationAsWordInName")
public final class GraphQLTelemetry {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.graphql-java-12.0";
/** Returns a new {@link GraphQLTelemetry} configured with the given {@link OpenTelemetry}. */
public static GraphQLTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}
/**
* Returns a new {@link GraphQLTelemetryBuilder} configured with the given {@link OpenTelemetry}.
*/
public static GraphQLTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new GraphQLTelemetryBuilder(openTelemetry);
}
private final Instrumenter<InstrumentationExecutionParameters, ExecutionResult> instrumenter;
private final boolean sanitizeQuery;
GraphQLTelemetry(OpenTelemetry openTelemetry, boolean sanitizeQuery) {
InstrumenterBuilder<InstrumentationExecutionParameters, ExecutionResult> builder =
Instrumenter.<InstrumentationExecutionParameters, ExecutionResult>builder(
openTelemetry, INSTRUMENTATION_NAME, ignored -> "GraphQL Operation")
.setSpanStatusExtractor(
(spanStatusBuilder, instrumentationExecutionParameters, executionResult, error) -> {
if (!executionResult.getErrors().isEmpty()) {
spanStatusBuilder.setStatus(StatusCode.ERROR);
} else {
SpanStatusExtractor.getDefault()
.extract(
spanStatusBuilder,
instrumentationExecutionParameters,
executionResult,
error);
}
});
builder.addAttributesExtractor(new GraphqlAttributesExtractor());
this.instrumenter = builder.buildInstrumenter();
this.sanitizeQuery = sanitizeQuery;
}
/**
* Returns a new {@link Instrumentation} that generates telemetry for received GraphQL requests.
*/
public Instrumentation newInstrumentation() {
return new OpenTelemetryInstrumentation(instrumenter, sanitizeQuery);
}
}

View File

@ -0,0 +1,26 @@
plugins {
id("otel.javaagent-instrumentation")
}
muzzle {
pass {
group.set("com.graphql-java")
module.set("graphql-java")
versions.set("[12,20)")
skip("230521-nf-execution")
assertInverse.set(true)
}
}
dependencies {
implementation(project(":instrumentation:graphql-java:graphql-java-12.0:library"))
implementation(project(":instrumentation:graphql-java:graphql-java-common:library"))
library("com.graphql-java:graphql-java:12.0")
testInstrumentation(project(":instrumentation:graphql-java:graphql-java-20.0:javaagent"))
testImplementation(project(":instrumentation:graphql-java:graphql-java-common:testing"))
latestDepTestLibrary("com.graphql-java:graphql-java:19.+")
}

View File

@ -17,7 +17,7 @@ import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class GraphqlInstrumentation implements TypeInstrumentation {
class GraphqlInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {

View File

@ -5,11 +5,15 @@
package io.opentelemetry.javaagent.instrumentation.graphql.v12_0;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.not;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
@SuppressWarnings("unused")
@AutoService(InstrumentationModule.class)
@ -19,6 +23,13 @@ public class GraphqlInstrumentationModule extends InstrumentationModule {
super("graphql-java", "graphql-java-12.0");
}
@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// added in 20.0
return not(
hasClassesNamed("graphql.execution.instrumentation.SimplePerformantInstrumentation"));
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new GraphqlInstrumentation());

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v12_0;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.graphql.internal.InstrumentationUtil;
import io.opentelemetry.instrumentation.graphql.v12_0.GraphQLTelemetry;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
public final class GraphqlSingletons {
private static final boolean QUERY_SANITIZATION_ENABLED =
InstrumentationConfig.get()
.getBoolean("otel.instrumentation.graphql.query-sanitizer.enabled", true);
private static final GraphQLTelemetry TELEMETRY =
GraphQLTelemetry.builder(GlobalOpenTelemetry.get())
.setSanitizeQuery(QUERY_SANITIZATION_ENABLED)
.build();
private GraphqlSingletons() {}
public static Instrumentation addInstrumentation(Instrumentation instrumentation) {
Instrumentation ourInstrumentation = TELEMETRY.newInstrumentation();
return InstrumentationUtil.addInstrumentation(instrumentation, ourInstrumentation);
}
}

View File

@ -1,4 +1,4 @@
# Library Instrumentation for GraphQL Java version 12.0 and higher
# Library Instrumentation for GraphQL Java version 12.0 to 20.0
Provides OpenTelemetry instrumentation for [GraphQL Java](https://www.graphql-java.com/).

View File

@ -0,0 +1,12 @@
plugins {
id("otel.library-instrumentation")
}
dependencies {
library("com.graphql-java:graphql-java:12.0")
implementation(project(":instrumentation:graphql-java:graphql-java-common:library"))
testImplementation(project(":instrumentation:graphql-java:graphql-java-common:testing"))
latestDepTestLibrary("com.graphql-java:graphql-java:19.+")
}

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;
@SuppressWarnings("AbbreviationAsWordInName")
public final class GraphQLTelemetry {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.graphql-java-12.0";
/** Returns a new {@link GraphQLTelemetry} configured with the given {@link OpenTelemetry}. */
public static GraphQLTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}
/**
* Returns a new {@link GraphQLTelemetryBuilder} configured with the given {@link OpenTelemetry}.
*/
public static GraphQLTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new GraphQLTelemetryBuilder(openTelemetry);
}
private final OpenTelemetryInstrumentationHelper helper;
GraphQLTelemetry(OpenTelemetry openTelemetry, boolean sanitizeQuery) {
helper =
OpenTelemetryInstrumentationHelper.create(
openTelemetry, INSTRUMENTATION_NAME, sanitizeQuery);
}
/**
* Returns a new {@link Instrumentation} that generates telemetry for received GraphQL requests.
*/
public Instrumentation newInstrumentation() {
return new OpenTelemetryInstrumentation(helper);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
import graphql.ExecutionResult;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.schema.DataFetcher;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationState;
final class OpenTelemetryInstrumentation extends SimpleInstrumentation {
private final OpenTelemetryInstrumentationHelper helper;
OpenTelemetryInstrumentation(OpenTelemetryInstrumentationHelper helper) {
this.helper = helper;
}
@Override
public InstrumentationState createState() {
return new OpenTelemetryInstrumentationState();
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters) {
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
return helper.beginExecution(state);
}
@Override
public InstrumentationContext<ExecutionResult> beginExecuteOperation(
InstrumentationExecuteOperationParameters parameters) {
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
return helper.beginExecuteOperation(parameters, state);
}
@Override
public DataFetcher<?> instrumentDataFetcher(
DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
return helper.instrumentDataFetcher(dataFetcher, state);
}
}

View File

@ -0,0 +1,24 @@
plugins {
id("otel.javaagent-instrumentation")
}
muzzle {
pass {
group.set("com.graphql-java")
module.set("graphql-java")
versions.set("[20,)")
skip("230521-nf-execution")
assertInverse.set(true)
}
}
dependencies {
implementation(project(":instrumentation:graphql-java:graphql-java-20.0:library"))
implementation(project(":instrumentation:graphql-java:graphql-java-common:library"))
library("com.graphql-java:graphql-java:20.0")
testInstrumentation(project(":instrumentation:graphql-java:graphql-java-12.0:javaagent"))
testImplementation(project(":instrumentation:graphql-java:graphql-java-common:testing"))
}

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v20_0;
import static io.opentelemetry.javaagent.instrumentation.graphql.v20_0.GraphqlSingletons.addInstrumentation;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
class GraphqlInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("graphql.GraphQL");
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
namedOneOf("checkInstrumentationDefaultState", "checkInstrumentation")
.and(returns(named("graphql.execution.instrumentation.Instrumentation"))),
this.getClass().getName() + "$AddInstrumentationAdvice");
}
@SuppressWarnings("unused")
public static class AddInstrumentationAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.Return(readOnly = false) Instrumentation instrumentation) {
instrumentation = addInstrumentation(instrumentation);
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v20_0;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
@SuppressWarnings("unused")
@AutoService(InstrumentationModule.class)
public class GraphqlInstrumentationModule extends InstrumentationModule {
public GraphqlInstrumentationModule() {
super("graphql-java", "graphql-java-20.0");
}
@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// added in 20.0
return hasClassesNamed("graphql.execution.instrumentation.SimplePerformantInstrumentation");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new GraphqlInstrumentation());
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v20_0;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.graphql.internal.InstrumentationUtil;
import io.opentelemetry.instrumentation.graphql.v20_0.GraphQLTelemetry;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
public final class GraphqlSingletons {
private static final boolean QUERY_SANITIZATION_ENABLED =
InstrumentationConfig.get()
.getBoolean("otel.instrumentation.graphql.query-sanitizer.enabled", true);
private static final GraphQLTelemetry TELEMETRY =
GraphQLTelemetry.builder(GlobalOpenTelemetry.get())
.setSanitizeQuery(QUERY_SANITIZATION_ENABLED)
.build();
private GraphqlSingletons() {}
public static Instrumentation addInstrumentation(Instrumentation instrumentation) {
Instrumentation ourInstrumentation = TELEMETRY.newInstrumentation();
return InstrumentationUtil.addInstrumentation(instrumentation, ourInstrumentation);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v20_0;
import graphql.GraphQL;
import io.opentelemetry.instrumentation.graphql.AbstractGraphqlTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
public class GraphqlTest extends AbstractGraphqlTest {
@RegisterExtension
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@Override
protected InstrumentationExtension getTesting() {
return testing;
}
@Override
protected void configure(GraphQL.Builder builder) {}
}

View File

@ -0,0 +1,40 @@
# Library Instrumentation for GraphQL Java version 20.0 and higher
Provides OpenTelemetry instrumentation for [GraphQL Java](https://www.graphql-java.com/).
## Quickstart
### Add these dependencies to your project
Replace `OPENTELEMETRY_VERSION` with the [latest
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-graphql-java-12.0).
For Maven, add to your `pom.xml` dependencies:
```xml
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-graphql-java-20.0</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```
For Gradle, add to your dependencies:
```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-graphql-java-20.0:OPENTELEMETRY_VERSION")
```
### Usage
The instrumentation library provides a GraphQL Java `Instrumentation` implementation that can be
added to an instance of the `GraphQL` to provide OpenTelemetry-based spans.
```java
void configure(OpenTelemetry openTelemetry, GraphQL.Builder builder) {
GraphQLTelemetry telemetry = GraphQLTelemetry.builder(openTelemetry).build();
builder.instrumentation(telemetry.newInstrumentation());
}
```

View File

@ -0,0 +1,10 @@
plugins {
id("otel.library-instrumentation")
}
dependencies {
library("com.graphql-java:graphql-java:20.0")
implementation(project(":instrumentation:graphql-java:graphql-java-common:library"))
testImplementation(project(":instrumentation:graphql-java:graphql-java-common:testing"))
}

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v20_0;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;
@SuppressWarnings("AbbreviationAsWordInName")
public final class GraphQLTelemetry {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.graphql-java-20.0";
/** Returns a new {@link GraphQLTelemetry} configured with the given {@link OpenTelemetry}. */
public static GraphQLTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}
/**
* Returns a new {@link GraphQLTelemetryBuilder} configured with the given {@link OpenTelemetry}.
*/
public static GraphQLTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new GraphQLTelemetryBuilder(openTelemetry);
}
private final OpenTelemetryInstrumentationHelper helper;
GraphQLTelemetry(OpenTelemetry openTelemetry, boolean sanitizeQuery) {
helper =
OpenTelemetryInstrumentationHelper.create(
openTelemetry, INSTRUMENTATION_NAME, sanitizeQuery);
}
/**
* Returns a new {@link Instrumentation} that generates telemetry for received GraphQL requests.
*/
public Instrumentation newInstrumentation() {
return new OpenTelemetryInstrumentation(helper);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v20_0;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
/** A builder of {@link GraphQLTelemetry}. */
@SuppressWarnings("AbbreviationAsWordInName")
public final class GraphQLTelemetryBuilder {
private final OpenTelemetry openTelemetry;
private boolean sanitizeQuery = true;
GraphQLTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/** Sets whether sensitive information should be removed from queries. Default is {@code true}. */
@CanIgnoreReturnValue
public GraphQLTelemetryBuilder setSanitizeQuery(boolean sanitizeQuery) {
this.sanitizeQuery = sanitizeQuery;
return this;
}
/**
* Returns a new {@link GraphQLTelemetry} with the settings of this {@link
* GraphQLTelemetryBuilder}.
*/
public GraphQLTelemetry build() {
return new GraphQLTelemetry(openTelemetry, sanitizeQuery);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v20_0;
import static graphql.execution.instrumentation.InstrumentationState.ofState;
import graphql.ExecutionResult;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimplePerformantInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.schema.DataFetcher;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationHelper;
import io.opentelemetry.instrumentation.graphql.internal.OpenTelemetryInstrumentationState;
final class OpenTelemetryInstrumentation extends SimplePerformantInstrumentation {
private final OpenTelemetryInstrumentationHelper helper;
OpenTelemetryInstrumentation(OpenTelemetryInstrumentationHelper helper) {
this.helper = helper;
}
@Override
public InstrumentationState createState(InstrumentationCreateStateParameters parameters) {
return new OpenTelemetryInstrumentationState();
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters, InstrumentationState rawState) {
OpenTelemetryInstrumentationState state = ofState(rawState);
return helper.beginExecution(state);
}
@Override
public InstrumentationContext<ExecutionResult> beginExecuteOperation(
InstrumentationExecuteOperationParameters parameters, InstrumentationState rawState) {
OpenTelemetryInstrumentationState state = ofState(rawState);
return helper.beginExecuteOperation(parameters, state);
}
@Override
public DataFetcher<?> instrumentDataFetcher(
DataFetcher<?> dataFetcher,
InstrumentationFieldFetchParameters parameters,
InstrumentationState rawState) {
OpenTelemetryInstrumentationState state = ofState(rawState);
return helper.instrumentDataFetcher(dataFetcher, state);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v20_0;
import graphql.GraphQL;
import io.opentelemetry.instrumentation.graphql.AbstractGraphqlTest;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
public class GraphqlTest extends AbstractGraphqlTest {
@RegisterExtension
private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Override
protected InstrumentationExtension getTesting() {
return testing;
}
@Override
protected void configure(GraphQL.Builder builder) {
GraphQLTelemetry telemetry = GraphQLTelemetry.builder(testing.getOpenTelemetry()).build();
builder.instrumentation(telemetry.newInstrumentation());
}
}

View File

@ -0,0 +1,9 @@
plugins {
id("otel.javaagent-instrumentation")
}
dependencies {
implementation(project(":instrumentation:graphql-java:graphql-java-common:library"))
library("com.graphql-java:graphql-java:12.0")
}

View File

@ -4,6 +4,4 @@ plugins {
dependencies {
library("com.graphql-java:graphql-java:12.0")
testImplementation(project(":instrumentation:graphql-java-12.0:testing"))
}

View File

@ -3,10 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
package io.opentelemetry.instrumentation.graphql.internal;
import graphql.ExecutionResult;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
@ -14,8 +13,12 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import java.util.Locale;
import javax.annotation.Nullable;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
final class GraphqlAttributesExtractor
implements AttributesExtractor<InstrumentationExecutionParameters, ExecutionResult> {
implements AttributesExtractor<OpenTelemetryInstrumentationState, ExecutionResult> {
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/graphql.md
private static final AttributeKey<String> OPERATION_NAME =
AttributeKey.stringKey("graphql.operation.name");
@ -28,20 +31,19 @@ final class GraphqlAttributesExtractor
public void onStart(
AttributesBuilder attributes,
Context parentContext,
InstrumentationExecutionParameters request) {}
OpenTelemetryInstrumentationState request) {}
@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
InstrumentationExecutionParameters request,
OpenTelemetryInstrumentationState request,
@Nullable ExecutionResult response,
@Nullable Throwable error) {
OpenTelemetryInstrumentationState state = request.getInstrumentationState();
attributes.put(OPERATION_NAME, state.getOperationName());
if (state.getOperation() != null) {
attributes.put(OPERATION_TYPE, state.getOperation().name().toLowerCase(Locale.ROOT));
attributes.put(OPERATION_NAME, request.getOperationName());
if (request.getOperation() != null) {
attributes.put(OPERATION_TYPE, request.getOperation().name().toLowerCase(Locale.ROOT));
}
attributes.put(GRAPHQL_DOCUMENT, state.getQuery());
attributes.put(GRAPHQL_DOCUMENT, request.getQuery());
}
}

View File

@ -3,31 +3,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.graphql.v12_0;
package io.opentelemetry.instrumentation.graphql.internal;
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.graphql.v12_0.GraphQLTelemetry;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
import java.util.ArrayList;
import java.util.List;
public final class GraphqlSingletons {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class InstrumentationUtil {
private static final boolean QUERY_SANITIZATION_ENABLED =
InstrumentationConfig.get()
.getBoolean("otel.instrumentation.graphql.query-sanitizer.enabled", true);
private static final GraphQLTelemetry TELEMETRY =
GraphQLTelemetry.builder(GlobalOpenTelemetry.get())
.setSanitizeQuery(QUERY_SANITIZATION_ENABLED)
.build();
private GraphqlSingletons() {}
public static Instrumentation addInstrumentation(Instrumentation instrumentation) {
Instrumentation ourInstrumentation = TELEMETRY.newInstrumentation();
public static Instrumentation addInstrumentation(
Instrumentation instrumentation, Instrumentation ourInstrumentation) {
if (instrumentation == null) {
return ourInstrumentation;
}
@ -47,4 +37,6 @@ public final class GraphqlSingletons {
}
return new ChainedInstrumentation(instrumentationList);
}
private InstrumentationUtil() {}
}

View File

@ -3,17 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
package io.opentelemetry.instrumentation.graphql.internal;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.SimpleInstrumentationContext;
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.language.AstPrinter;
import graphql.language.AstTransformer;
import graphql.language.BooleanValue;
@ -23,52 +19,75 @@ import graphql.language.NodeVisitor;
import graphql.language.NodeVisitorStub;
import graphql.language.NullValue;
import graphql.language.OperationDefinition;
import graphql.language.OperationDefinition.Operation;
import graphql.language.Value;
import graphql.language.VariableReference;
import graphql.schema.DataFetcher;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import graphql.util.TreeTransformerUtil;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.Locale;
final class OpenTelemetryInstrumentation extends SimpleInstrumentation {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class OpenTelemetryInstrumentationHelper {
private static final NodeVisitor sanitizingVisitor = new SanitizingVisitor();
private static final AstTransformer astTransformer = new AstTransformer();
private final Instrumenter<InstrumentationExecutionParameters, ExecutionResult> instrumenter;
private final Instrumenter<OpenTelemetryInstrumentationState, ExecutionResult> instrumenter;
private final boolean sanitizeQuery;
OpenTelemetryInstrumentation(
Instrumenter<InstrumentationExecutionParameters, ExecutionResult> instrumenter,
private OpenTelemetryInstrumentationHelper(
Instrumenter<OpenTelemetryInstrumentationState, ExecutionResult> instrumenter,
boolean sanitizeQuery) {
this.instrumenter = instrumenter;
this.sanitizeQuery = sanitizeQuery;
}
@Override
public InstrumentationState createState() {
return new OpenTelemetryInstrumentationState();
public static OpenTelemetryInstrumentationHelper create(
OpenTelemetry openTelemetry, String instrumentationName, boolean sanitizeQuery) {
InstrumenterBuilder<OpenTelemetryInstrumentationState, ExecutionResult> builder =
Instrumenter.<OpenTelemetryInstrumentationState, ExecutionResult>builder(
openTelemetry, instrumentationName, ignored -> "GraphQL Operation")
.setSpanStatusExtractor(
(spanStatusBuilder, instrumentationExecutionParameters, executionResult, error) -> {
if (!executionResult.getErrors().isEmpty()) {
spanStatusBuilder.setStatus(StatusCode.ERROR);
} else {
SpanStatusExtractor.getDefault()
.extract(
spanStatusBuilder,
instrumentationExecutionParameters,
executionResult,
error);
}
});
builder.addAttributesExtractor(new GraphqlAttributesExtractor());
return new OpenTelemetryInstrumentationHelper(builder.buildInstrumenter(), sanitizeQuery);
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters) {
OpenTelemetryInstrumentationState state) {
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, parameters)) {
if (!instrumenter.shouldStart(parentContext, state)) {
return SimpleInstrumentationContext.noOp();
}
Context context = instrumenter.start(parentContext, parameters);
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
Context context = instrumenter.start(parentContext, state);
state.setContext(context);
return SimpleInstrumentationContext.whenCompleted(
@ -82,20 +101,18 @@ final class OpenTelemetryInstrumentation extends SimpleInstrumentation {
span.addEvent(SemanticAttributes.EXCEPTION_EVENT_NAME, attributes.build());
}
instrumenter.end(context, parameters, result, throwable);
instrumenter.end(context, state, result, throwable);
});
}
@Override
public InstrumentationContext<ExecutionResult> beginExecuteOperation(
InstrumentationExecuteOperationParameters parameters) {
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
InstrumentationExecuteOperationParameters parameters,
OpenTelemetryInstrumentationState state) {
Span span = Span.fromContext(state.getContext());
OperationDefinition operationDefinition =
parameters.getExecutionContext().getOperationDefinition();
Operation operation = operationDefinition.getOperation();
OperationDefinition.Operation operation = operationDefinition.getOperation();
String operationType = operation.name().toLowerCase(Locale.ROOT);
String operationName = operationDefinition.getName();
@ -117,10 +134,8 @@ final class OpenTelemetryInstrumentation extends SimpleInstrumentation {
return SimpleInstrumentationContext.noOp();
}
@Override
public DataFetcher<?> instrumentDataFetcher(
DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
OpenTelemetryInstrumentationState state = parameters.getInstrumentationState();
DataFetcher<?> dataFetcher, OpenTelemetryInstrumentationState state) {
Context context = state.getContext();
return (DataFetcher<Object>)

View File

@ -3,13 +3,17 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.graphql.v12_0;
package io.opentelemetry.instrumentation.graphql.internal;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.language.OperationDefinition.Operation;
import io.opentelemetry.context.Context;
final class OpenTelemetryInstrumentationState implements InstrumentationState {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class OpenTelemetryInstrumentationState implements InstrumentationState {
private Context context;
private Operation operation;
private String operationName;

View File

@ -180,7 +180,7 @@ public abstract class AbstractGraphqlTest {
equalTo(AttributeKey.stringKey("graphql.operation.type"), "query"),
normalizedQueryEqualsTo(
AttributeKey.stringKey("graphql.document"),
"query { bookById(id: ?) { name } }")),
"{ bookById(id: ?) { name } }")),
span -> span.hasName("fetchBookById").hasParent(trace.getSpan(0))));
}
@ -211,7 +211,8 @@ public abstract class AbstractGraphqlTest {
satisfies(
SemanticAttributes.EXCEPTION_MESSAGE,
message ->
message.startsWith("Invalid Syntax"))))));
message.startsWithIgnoringCase(
"Invalid Syntax"))))));
}
@Test
@ -252,8 +253,7 @@ public abstract class AbstractGraphqlTest {
satisfies(
SemanticAttributes.EXCEPTION_MESSAGE,
message ->
message.startsWith(
"Validation error of type FieldUndefined"))))));
message.startsWith("Validation error"))))));
}
@Test
@ -295,6 +295,9 @@ public abstract class AbstractGraphqlTest {
stringAssert.satisfies(
querySource -> {
String normalized = querySource.replaceAll("(?s)\\s+", " ");
if (normalized.startsWith("query {")) {
normalized = normalized.substring("query ".length());
}
assertThat(normalized).isEqualTo(value);
}));
}

View File

@ -191,9 +191,12 @@ include(":instrumentation:cassandra:cassandra-4.4:library")
include(":instrumentation:cassandra:cassandra-4.4:testing")
include(":instrumentation:cassandra:cassandra-4-common:testing")
include(":instrumentation:cdi-testing")
include(":instrumentation:graphql-java-12.0:javaagent")
include(":instrumentation:graphql-java-12.0:library")
include(":instrumentation:graphql-java-12.0:testing")
include(":instrumentation:graphql-java:graphql-java-12.0:javaagent")
include(":instrumentation:graphql-java:graphql-java-12.0:library")
include(":instrumentation:graphql-java:graphql-java-20.0:javaagent")
include(":instrumentation:graphql-java:graphql-java-20.0:library")
include(":instrumentation:graphql-java:graphql-java-common:library")
include(":instrumentation:graphql-java:graphql-java-common:testing")
include(":instrumentation:internal:internal-application-logger:bootstrap")
include(":instrumentation:internal:internal-application-logger:javaagent")
include(":instrumentation:internal:internal-class-loader:javaagent")