Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ TEST_INTEGRATIONS_80 := \
test_integrations_pdo \
test_integrations_elasticsearch7 \
test_integrations_googlespanner_latest \
test_integrations_graphql_latest \
test_integrations_guzzle5 \
test_integrations_guzzle6 \
test_integrations_guzzle_latest \
Expand Down Expand Up @@ -906,6 +907,7 @@ TEST_INTEGRATIONS_81 := \
test_opentelemetry_1 \
test_opentelemetry_beta \
test_integrations_googlespanner_latest \
test_integrations_graphql_latest \
test_integrations_guzzle_latest \
test_integrations_pcntl \
test_integrations_pdo \
Expand Down Expand Up @@ -964,6 +966,7 @@ TEST_INTEGRATIONS_82 := \
test_opentelemetry_1 \
test_opentelemetry_beta \
test_integrations_googlespanner_latest \
test_integrations_graphql_latest \
test_integrations_guzzle_latest \
test_integrations_pcntl \
test_integrations_pdo \
Expand Down Expand Up @@ -1030,6 +1033,7 @@ TEST_INTEGRATIONS_83 := \
test_opentelemetry_1 \
test_opentelemetry_beta \
test_integrations_googlespanner_latest \
test_integrations_graphql_latest \
test_integrations_guzzle_latest \
test_integrations_pcntl \
test_integrations_pdo \
Expand Down Expand Up @@ -1090,6 +1094,7 @@ TEST_INTEGRATIONS_84 := \
test_integrations_openai_latest \
test_opentelemetry_1 \
test_integrations_googlespanner_latest \
test_integrations_graphql_latest \
test_integrations_guzzle_latest \
test_integrations_pcntl \
test_integrations_pdo \
Expand Down Expand Up @@ -1367,6 +1372,8 @@ test_integrations_googlespanner_latest: global_test_run_dependencies tests/Integ
$(call run_tests_debug,tests/Integrations/GoogleSpanner/Latest)
$(eval TEST_EXTRA_INI=)
$(eval TEST_EXTRA_ENV=)
test_integrations_graphql_latest: global_test_run_dependencies tests/Integrations/GraphQL/Latest/composer.lock-php$(PHP_MAJOR_MINOR)
$(call run_tests_debug,tests/Integrations/GraphQL/Latest)
test_integrations_sqlsrv: global_test_run_dependencies
$(eval TEST_EXTRA_INI=-d extension=sqlsrv.so)
$(call run_tests_debug,tests/Integrations/SQLSRV)
Expand Down
7 changes: 7 additions & 0 deletions ext/integrations/integrations.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,13 @@ void ddtrace_integrations_minit(void) {
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_GOOGLESPANNER, "Google\\Cloud\\Spanner\\Database", "__construct",
"DDTrace\\Integrations\\GoogleSpanner\\GoogleSpannerIntegration");

DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_GRAPHQL, "GraphQL\\GraphQL", "promiseToExecute",
"DDTrace\\Integrations\\GraphQL\\GraphQLIntegration");
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_GRAPHQL, "GraphQL\\Validator\\DocumentValidator", "validate",
"DDTrace\\Integrations\\GraphQL\\GraphQLIntegration");
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_GRAPHQL, "GraphQL\\Language\\Parser", "parse",
"DDTrace\\Integrations\\GraphQL\\GraphQLIntegration");

DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_GUZZLE, "GuzzleHttp\\Client", "__construct",
"DDTrace\\Integrations\\Guzzle\\GuzzleIntegration");

Expand Down
3 changes: 2 additions & 1 deletion ext/integrations/integrations.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
INTEGRATION_CUSTOM_ENABLED(FILESYSTEM, "filesystem", is_filesystem_enabled) \
INTEGRATION(FRANKENPHP, "frankenphp") \
INTEGRATION(GOOGLESPANNER, "googlespanner") \
INTEGRATION(GRAPHQL, "graphql") \
INTEGRATION(GUZZLE, "guzzle") \
INTEGRATION(KAFKA, "kafka") \
INTEGRATION(LAMINAS, "laminas") \
Expand All @@ -43,7 +44,7 @@
INTEGRATION(PHPREDIS, "phpredis") \
INTEGRATION(PREDIS, "predis") \
INTEGRATION(PSR18, "psr18") \
INTEGRATION(RATCHET, "ratchet") \
INTEGRATION(RATCHET, "ratchet") \
INTEGRATION(ROADRUNNER, "roadrunner") \
INTEGRATION(SQLSRV, "sqlsrv") \
INTEGRATION(SLIM, "slim") \
Expand Down
136 changes: 136 additions & 0 deletions src/DDTrace/Integrations/GraphQL/GraphQLIntegration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

namespace DDTrace\Integrations\GraphQL;

use DDTrace\Integrations\Integration;
use DDTrace\SpanData;
use DDTrace\Tag;
use DDTrace\Type;
use GraphQL\Language\AST\OperationDefinitionNode;

class GraphQLIntegration extends Integration
{
const NAME = 'graphql';

public function init(): int
{
$integration = $this;

\DDTrace\trace_method(
'GraphQL\Executor\Executor',
'promiseToExecute',
function (SpanData $span, $args) use ($integration) {
$span->name = 'graphql.execute';
$span->type = Type::GRAPHQL;
$span->meta[Tag::COMPONENT] = GraphQLIntegration::NAME;

// Args are:
// 0: PromiseAdapter $promiseAdapter
// 1: Schema $schema
// 2: DocumentNode $documentNode
// 3: $rootValue = null
// 4: $contextValue = null
// 5: ?array $variableValues = null
// 6: ?string $operationName = null
// 7: ?callable $fieldResolver = null
// 8: ?callable $argsMapper = null

// Set graphql.source from the document node
if (isset($args[2]) && isset($args[2]->loc) && isset($args[2]->loc->source)) {
$span->meta['graphql.source'] = $args[2]->loc->source->body;
}

// Find the operation definition
if (isset($args[2]) && isset($args[2]->definitions)) {
$operationName = $args[6] ?? null;
$operationDefinition = null;

// If operation name is provided, find the matching operation
if ($operationName !== null) {
foreach ($args[2]->definitions as $definition) {
if ($definition instanceof OperationDefinitionNode &&
isset($definition->name) &&
$definition->name->value === $operationName) {
$operationDefinition = $definition;
break;
}
}
}

// If no operation name or no matching operation found, use the first operation definition
if ($operationDefinition === null) {
foreach ($args[2]->definitions as $definition) {
if ($definition instanceof OperationDefinitionNode) {
$operationDefinition = $definition;
break;
}
}
}

// Set operation type and name if we found an operation definition
if ($operationDefinition !== null) {
if (isset($operationDefinition->operation)) {
$span->meta['graphql.operation.type'] = $operationDefinition->operation;
}
if (isset($operationDefinition->name->value)) {
$span->meta['graphql.operation.name'] = $operationDefinition->name->value;
}
}
}

// Set graphql.variables from the variable values
if (isset($args[5])) {
foreach ($args[5] as $key => $value) {
$span->meta["graphql.variables.$key"] = is_scalar($value) ? $value : json_encode($value);
}
}
}
);

\DDTrace\trace_method(
'GraphQL\Validator\DocumentValidator',
'validate',
function (SpanData $span, $args) use ($integration) {
$span->name = 'graphql.validate';
$span->type = Type::GRAPHQL;
$span->meta[Tag::COMPONENT] = GraphQLIntegration::NAME;

// Args are:
// 0: Schema $schema
// 1: DocumentNode $ast
// 2: array $rules = null
// 3: array $typeInfo = null

// Set graphql.source from the document node
if (isset($args[1]) && isset($args[1]->loc) && isset($args[1]->loc->source)) {
$span->meta['graphql.source'] = $args[1]->loc->source->body;
}
}
);

\DDTrace\trace_method(
'GraphQL\Language\Parser',
'parse',
function (SpanData $span, $args) use ($integration) {
$span->name = 'graphql.parse';
$span->type = Type::GRAPHQL;
$span->meta[Tag::COMPONENT] = GraphQLIntegration::NAME;

// Args are:
// 0: Source|string $source
// 1: array $options = []

// Set graphql.source
if (isset($args[0])) {
if (is_string($args[0])) {
$span->meta['graphql.source'] = $args[0];
} elseif (is_object($args[0]) && isset($args[0]->body)) {
$span->meta['graphql.source'] = $args[0]->body;
}
}
}
);

return Integration::LOADED;
}
}
2 changes: 2 additions & 0 deletions src/api/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ class Type
const REDIS = 'redis';

const SYSTEM = 'system';

const GRAPHQL = 'graphql';
}
152 changes: 152 additions & 0 deletions tests/Integrations/GraphQL/Latest/GraphQLIntegrationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace DDTrace\Tests\Integrations\GraphQL\Latest;

use DDTrace\Tag;
use DDTrace\Tests\Common\SnapshotTestTrait;
use DDTrace\Tests\Common\IntegrationTestCase;
use DDTrace\Tests\Common\SpanAssertion;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

class GraphQLIntegrationTest extends IntegrationTestCase
{
use SnapshotTestTrait;

public function testBasicQuery()
{
$traces = $this->isolateTracer(function () {
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'hello' => [
'type' => Type::string(),
'resolve' => function () {
return 'world';
}
]
]
])
]);

$query = 'query { hello }';
$result = GraphQL::executeQuery($schema, $query);
});

$this->assertSpans($traces, [
SpanAssertion::build('graphql.parse', 'phpunit', 'graphql', 'graphql.parse')
->withExactTags([
'graphql.source' => 'query { hello }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.validate', 'phpunit', 'graphql', 'graphql.validate')
->withExactTags([
'graphql.source' => 'query { hello }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.execute', 'phpunit', 'graphql', 'graphql.execute')
->withExactTags([
'graphql.source' => 'query { hello }',
'graphql.operation.type' => 'query',
Tag::COMPONENT => 'graphql'
])
]);
}

public function testQueryWithVariables()
{
$traces = $this->isolateTracer(function () {
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'greet' => [
'type' => Type::string(),
'args' => [
'name' => ['type' => Type::string()]
],
'resolve' => function ($root, $args) {
return "Hello, {$args['name']}!";
}
]
]
])
]);

$query = 'query Greet($name: String!) { greet(name: $name) }';
$variables = ['name' => 'World'];
$result = GraphQL::executeQuery($schema, $query, null, null, $variables);
});

$this->assertSpans($traces, [
SpanAssertion::build('graphql.parse', 'phpunit', 'graphql', 'graphql.parse')
->withExactTags([
'graphql.source' => 'query Greet($name: String!) { greet(name: $name) }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.validate', 'phpunit', 'graphql', 'graphql.validate')
->withExactTags([
'graphql.source' => 'query Greet($name: String!) { greet(name: $name) }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.execute', 'phpunit', 'graphql', 'graphql.execute')
->withExactTags([
'graphql.source' => 'query Greet($name: String!) { greet(name: $name) }',
'graphql.operation.type' => 'query',
'graphql.operation.name' => 'Greet',
'graphql.variables.name' => 'World',
Tag::COMPONENT => 'graphql'
])
]);
}

public function testMutation()
{
$traces = $this->isolateTracer(function () {
$schema = new Schema([
'mutation' => new ObjectType([
'name' => 'Mutation',
'fields' => [
'createUser' => [
'type' => Type::string(),
'args' => [
'name' => ['type' => Type::string()]
],
'resolve' => function ($root, $args) {
return "Created user: {$args['name']}";
}
]
]
])
]);

$query = 'mutation CreateUser($name: String!) { createUser(name: $name) }';
$variables = ['name' => 'John'];
$result = GraphQL::executeQuery($schema, $query, null, null, $variables);
});

$this->assertSpans($traces, [
SpanAssertion::build('graphql.parse', 'phpunit', 'graphql', 'graphql.parse')
->withExactTags([
'graphql.source' => 'mutation CreateUser($name: String!) { createUser(name: $name) }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.validate', 'phpunit', 'graphql', 'graphql.validate')
->withExactTags([
'graphql.source' => 'mutation CreateUser($name: String!) { createUser(name: $name) }',
Tag::COMPONENT => 'graphql'
]),
SpanAssertion::build('graphql.execute', 'phpunit', 'graphql', 'graphql.execute')
->withExactTags([
'graphql.source' => 'mutation CreateUser($name: String!) { createUser(name: $name) }',
'graphql.operation.type' => 'mutation',
'graphql.operation.name' => 'CreateUser',
'graphql.variables.name' => 'John',
Tag::COMPONENT => 'graphql'
])
]);
}
}
5 changes: 5 additions & 0 deletions tests/Integrations/GraphQL/Latest/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require": {
"webonyx/graphql-php": "15.20.0"
}
}
Loading