-
Notifications
You must be signed in to change notification settings - Fork 910
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
GraphQL java instrumentation #5583
Changes from 10 commits
f016d7b
1e953c4
48382ab
9b94a78
a1904fe
72319b8
a7d72b8
dfc5f9d
e5f7e2a
5a13760
2eac6b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Settings for the GraphQL instrumentation | ||
|
||
| System property | Type | Default | Description | | ||
|---|---|---------|--------------------------------------------------------------------------------------------| | ||
| `otel.instrumentation.graphql.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | | ||
| `otel.instrumentation.graphql.query-sanitizer.enabled` | Boolean | `true` | Whether to remove sensitive information from query source that is added as span attribute. | |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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")) | ||
} | ||
|
||
tasks.withType<Test>().configureEach { | ||
jvmArgs("-Dotel.instrumentation.graphql.experimental-span-attributes=true") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.graphql; | ||
|
||
import static io.opentelemetry.javaagent.instrumentation.graphql.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; | ||
|
||
public class GraphqlInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return named("graphql.GraphQL"); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
namedOneOf("checkInstrumentationDefaultState", "checkInstrumentation") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These methods add default instrumentations. By applying our instrumentation after these our instrumentation will end up surrounding all other instrumentation. |
||
.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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.graphql; | ||
|
||
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; | ||
|
||
@SuppressWarnings("unused") | ||
@AutoService(InstrumentationModule.class) | ||
public class GraphqlInstrumentationModule extends InstrumentationModule { | ||
|
||
public GraphqlInstrumentationModule() { | ||
super("graphql-java", "graphql-java-12.0"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return Collections.singletonList(new GraphqlInstrumentation()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.graphql; | ||
|
||
import graphql.execution.instrumentation.ChainedInstrumentation; | ||
import graphql.execution.instrumentation.Instrumentation; | ||
import io.opentelemetry.api.GlobalOpenTelemetry; | ||
import io.opentelemetry.instrumentation.api.config.Config; | ||
import io.opentelemetry.instrumentation.graphql.GraphQLTracing; | ||
import io.opentelemetry.instrumentation.graphql.OpenTelemetryInstrumentation; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public final class GraphqlSingletons { | ||
|
||
private static final boolean QUERY_SANITIZATION_ENABLED = | ||
Config.get().getBoolean("otel.instrumentation.graphql.query-sanitizer.enabled", true); | ||
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = | ||
Config.get().getBoolean("otel.instrumentation.graphql.experimental-span-attributes", false); | ||
|
||
private static final GraphQLTracing TRACING = | ||
GraphQLTracing.builder(GlobalOpenTelemetry.get()) | ||
.setCaptureExperimentalSpanAttributes(CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) | ||
.setSanitizeQuery(QUERY_SANITIZATION_ENABLED) | ||
.build(); | ||
|
||
private GraphqlSingletons() {} | ||
|
||
public static Instrumentation addInstrumentation(Instrumentation instrumentation) { | ||
Instrumentation ourInstrumentation = TRACING.newInstrumentation(); | ||
if (instrumentation == null) { | ||
return ourInstrumentation; | ||
} | ||
if (instrumentation instanceof OpenTelemetryInstrumentation) { | ||
return instrumentation; | ||
} | ||
List<Instrumentation> instrumentationList = new ArrayList<>(); | ||
if (instrumentation instanceof ChainedInstrumentation) { | ||
instrumentationList.addAll(((ChainedInstrumentation) instrumentation).getInstrumentations()); | ||
} else { | ||
instrumentationList.add(instrumentation); | ||
} | ||
boolean containsOurInstrumentation = | ||
instrumentationList.stream().anyMatch(OpenTelemetryInstrumentation.class::isInstance); | ||
if (!containsOurInstrumentation) { | ||
instrumentationList.add(0, ourInstrumentation); | ||
} | ||
return new ChainedInstrumentation(instrumentationList); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.graphql; | ||
|
||
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) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Manual Instrumentation for GraphQL Java | ||
|
||
Provides OpenTelemetry instrumentation for [GraphQL Java](https://www.graphql-java.com/). | ||
|
||
## Quickstart | ||
|
||
### Add these dependencies to your project: | ||
|
||
Replace `OPENTELEMETRY_VERSION` with the latest stable | ||
[release](https://mvnrepository.com/artifact/io.opentelemetry). `Minimum version: 1.13.0` | ||
|
||
For Maven, add to your `pom.xml` dependencies: | ||
|
||
```xml | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.opentelemetry.instrumentation</groupId> | ||
<artifactId>opentelemetry-graphql-java-12.0</artifactId> | ||
<version>OPENTELEMETRY_VERSION</version> | ||
</dependency> | ||
</dependencies> | ||
``` | ||
|
||
For Gradle, add to your dependencies: | ||
|
||
```groovy | ||
implementation("io.opentelemetry.instrumentation:opentelemetry-graphql-java-12.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) { | ||
GraphQLTracing tracing = GraphQLTracing.builder(openTelemetry).build(); | ||
builder.instrumentation(tracing.newInstrumentation()); | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
plugins { | ||
id("otel.library-instrumentation") | ||
} | ||
|
||
dependencies { | ||
library("com.graphql-java:graphql-java:12.0") | ||
|
||
testImplementation(project(":instrumentation:graphql-java-12.0:testing")) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.graphql; | ||
|
||
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; | ||
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; | ||
import javax.annotation.Nullable; | ||
|
||
final class ExperimentalAttributesExtractor | ||
implements AttributesExtractor<InstrumentationExecutionParameters, ExecutionResult> { | ||
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-graphql/src/enums/AttributeNames.ts | ||
private static final AttributeKey<String> OPERATION_NAME = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these attributes are the same as in js instrumentation |
||
AttributeKey.stringKey("graphql.operation.name"); | ||
private static final AttributeKey<String> OPERATION_TYPE = | ||
AttributeKey.stringKey("graphql.operation.type"); | ||
private static final AttributeKey<String> GRAPHQL_SOURCE = | ||
AttributeKey.stringKey("graphql.source"); | ||
|
||
@Override | ||
public void onStart( | ||
AttributesBuilder attributes, | ||
Context parentContext, | ||
InstrumentationExecutionParameters request) {} | ||
|
||
@Override | ||
public void onEnd( | ||
AttributesBuilder attributes, | ||
Context context, | ||
InstrumentationExecutionParameters 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()); | ||
} | ||
attributes.put(GRAPHQL_SOURCE, state.getQuery()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.graphql; | ||
|
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as discussed on slack, for library instrumentation I used |
||
public final class GraphQLTracing { | ||
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.graphql-java-12.0"; | ||
|
||
/** Returns a new {@link GraphQLTracing} configured with the given {@link OpenTelemetry}. */ | ||
public static GraphQLTracing create(OpenTelemetry openTelemetry) { | ||
return builder(openTelemetry).build(); | ||
} | ||
|
||
/** | ||
* Returns a new {@link GraphQLTracingBuilder} configured with the given {@link OpenTelemetry}. | ||
*/ | ||
public static GraphQLTracingBuilder builder(OpenTelemetry openTelemetry) { | ||
return new GraphQLTracingBuilder(openTelemetry); | ||
} | ||
|
||
private final Instrumenter<InstrumentationExecutionParameters, ExecutionResult> instrumenter; | ||
private final boolean captureExperimentalSpanAttributes; | ||
private final boolean sanitizeQuery; | ||
|
||
GraphQLTracing( | ||
OpenTelemetry openTelemetry, | ||
boolean captureExperimentalSpanAttributes, | ||
boolean sanitizeQuery) { | ||
InstrumenterBuilder<InstrumentationExecutionParameters, ExecutionResult> builder = | ||
Instrumenter.<InstrumentationExecutionParameters, ExecutionResult>builder( | ||
openTelemetry, INSTRUMENTATION_NAME, ignored -> "GraphQL Query") | ||
.setSpanStatusExtractor( | ||
(instrumentationExecutionParameters, executionResult, error) -> { | ||
if (!executionResult.getErrors().isEmpty()) { | ||
return StatusCode.ERROR; | ||
} | ||
return SpanStatusExtractor.getDefault() | ||
.extract(instrumentationExecutionParameters, executionResult, error); | ||
}); | ||
if (captureExperimentalSpanAttributes) { | ||
builder.addAttributesExtractor(new ExperimentalAttributesExtractor()); | ||
} | ||
|
||
this.instrumenter = builder.newInstrumenter(); | ||
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes; | ||
this.sanitizeQuery = sanitizeQuery; | ||
} | ||
|
||
/** | ||
* Returns a new {@link Instrumentation} that generates telemetry for received GraphQL requests. | ||
*/ | ||
public Instrumentation newInstrumentation() { | ||
return new OpenTelemetryInstrumentation( | ||
instrumenter, captureExperimentalSpanAttributes, sanitizeQuery); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
graphql-java has a ton of weird versions https://repo1.maven.org/maven2/com/graphql-java/graphql-java/