Skip to content

Commit 221c2fb

Browse files
authored
feat(graphql): support collapse, depth, variables, and error extensions via env (#9111)
This lets `initialize.mjs` users configure the graphql plugin without code changes, matching the env-var configuration other integrations already offer. `DD_TRACE_GRAPHQL_COLLAPSE`, `DD_TRACE_GRAPHQL_DEPTH`, and `DD_TRACE_GRAPHQL_VARIABLES` join the existing `DD_TRACE_GRAPHQL_ERROR_EXTENSIONS` on the Config singleton and are forwarded to the graphql plugin through plugin_manager, so they seed the plugin config as a base that a programmatic `tracer.use('graphql', …)` value overrides, and remote config and config telemetry observe them on Config like every other global option. `depth` rejects anything outside `-1` and the non-negative integers via its allowed pattern. 1. `errorExtensions` becomes a programmatic plugin option too, not env-only; the error-event extraction now reads the resolved plugin config instead of the raw env name off the tracer config. A value set both programmatically and via env now resolves programmatic-wins instead of env-always-wins. 2. An invalid programmatic `depth` falls back to the registered default `-1` after logging, rather than to the env value, because the merge collapses the global and programmatic values onto one key. 3. The `variables` env var accepts the array form only; the callback form stays programmatic. An empty `variables`/`errorExtensions` array means no filter. Fixes: #7546
1 parent c3db8f8 commit 221c2fb

13 files changed

Lines changed: 237 additions & 8 deletions

File tree

index.d.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2542,6 +2542,7 @@ declare namespace tracer {
25422542
* count toward the limit, regardless of `collapse`.
25432543
*
25442544
* @default -1
2545+
* @env DD_TRACE_GRAPHQL_DEPTH
25452546
*/
25462547
depth?: number;
25472548

@@ -2558,7 +2559,10 @@ declare namespace tracer {
25582559
/**
25592560
* An array of variable names to record. Can also be a callback that returns
25602561
* the key/value pairs to record. For example, using
2561-
* `variables => variables` would record all variables.
2562+
* `variables => variables` would record all variables. The environment
2563+
* variable only accepts the array form (comma-separated variable names).
2564+
*
2565+
* @env DD_TRACE_GRAPHQL_VARIABLES
25622566
*/
25632567
variables?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any });
25642568

@@ -2567,9 +2571,18 @@ declare namespace tracer {
25672571
* `users.*.name` span instead of `users.0.name`, `users.1.name`, etc)
25682572
*
25692573
* @default true
2574+
* @env DD_TRACE_GRAPHQL_COLLAPSE
25702575
*/
25712576
collapse?: boolean;
25722577

2578+
/**
2579+
* An array of error `extensions` keys to attach to the span error event
2580+
* for each GraphQL error.
2581+
*
2582+
* @env DD_TRACE_GRAPHQL_ERROR_EXTENSIONS
2583+
*/
2584+
errorExtensions?: string[];
2585+
25732586
/**
25742587
* Whether to enable signature calculation for the resource name. This can
25752588
* be disabled if your GraphQL operations always have a name. Note that when

index.d.v5.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2674,6 +2674,7 @@ declare namespace tracer {
26742674
* instrument the operation or to `-1` to instrument all fields/resolvers.
26752675
*
26762676
* @default -1
2677+
* @env DD_TRACE_GRAPHQL_DEPTH
26772678
*/
26782679
depth?: number;
26792680

@@ -2690,7 +2691,10 @@ declare namespace tracer {
26902691
/**
26912692
* An array of variable names to record. Can also be a callback that returns
26922693
* the key/value pairs to record. For example, using
2693-
* `variables => variables` would record all variables.
2694+
* `variables => variables` would record all variables. The environment
2695+
* variable only accepts the array form (comma-separated variable names).
2696+
*
2697+
* @env DD_TRACE_GRAPHQL_VARIABLES
26942698
*/
26952699
variables?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any });
26962700

@@ -2699,9 +2703,18 @@ declare namespace tracer {
26992703
* `users.*.name` span instead of `users.0.name`, `users.1.name`, etc)
27002704
*
27012705
* @default true
2706+
* @env DD_TRACE_GRAPHQL_COLLAPSE
27022707
*/
27032708
collapse?: boolean;
27042709

2710+
/**
2711+
* An array of error `extensions` keys to attach to the span error event
2712+
* for each GraphQL error.
2713+
*
2714+
* @env DD_TRACE_GRAPHQL_ERROR_EXTENSIONS
2715+
*/
2716+
errorExtensions?: string[];
2717+
27052718
/**
27062719
* Whether to enable signature calculation for the resource name. This can
27072720
* be disabled if your GraphQL operations always have a name. Note that when

packages/datadog-plugin-graphql/src/execute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class GraphQLExecutePlugin extends TracingPlugin {
241241
if (res?.errors?.length) {
242242
span.setTag('error', res.errors[0])
243243
for (const err of res.errors) {
244-
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
244+
extractErrorIntoSpanEvent(this.config, span, err)
245245
}
246246
}
247247

packages/datadog-plugin-graphql/src/index.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class GraphQLPlugin extends CompositePlugin {
3030
}
3131

3232
// config validator helpers
33+
//
34+
// `collapse`, `depth`, `variables`, and `errorExtensions` arrive pre-merged on
35+
// `config`: plugin_manager seeds the `DD_TRACE_GRAPHQL_*` env values (already
36+
// parsed to their declared type) as the base and a programmatic
37+
// `tracer.use('graphql', …)` value overrides them. So these helpers only shape
38+
// the merged value — coerce, validate, and turn `variables` into a filter — and
39+
// never read the environment themselves.
3340

3441
function validateConfig (config) {
3542
const collapse = config.collapse === undefined || !!config.collapse
@@ -41,6 +48,7 @@ function validateConfig (config) {
4148
// v5 counted collapsed list indices toward `depth`, so the same query reached a
4249
// different depth depending on `collapse`. v6 counts selection-set depth only.
4350
countListIndices: DD_MAJOR < 6 && collapse,
51+
errorExtensions: getErrorExtensions(config),
4452
hooks: getHooks(config),
4553
}
4654
}
@@ -58,13 +66,21 @@ function getVariablesFilter (config) {
5866
if (typeof config.variables === 'function') {
5967
return config.variables
6068
} else if (Array.isArray(config.variables)) {
61-
return variables => pick(variables, config.variables)
69+
return config.variables.length > 0 ? variables => pick(variables, config.variables) : null
6270
} else if (config.hasOwnProperty('variables')) {
6371
log.error('Expected `variables` to be an array or function.')
6472
}
6573
return null
6674
}
6775

76+
function getErrorExtensions (config) {
77+
if (Array.isArray(config.errorExtensions)) {
78+
return config.errorExtensions.length > 0 ? config.errorExtensions : undefined
79+
} else if (config.hasOwnProperty('errorExtensions')) {
80+
log.error('Expected `errorExtensions` to be an array.')
81+
}
82+
}
83+
6884
const noop = () => {}
6985
const noopHooks = { execute: noop, parse: noop, validate: noop, resolve: undefined }
7086

packages/datadog-plugin-graphql/src/utils.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
'use strict'
22

3+
/**
4+
* @param {{ errorExtensions?: string[] }} config Resolved plugin config; `errorExtensions` lists the
5+
* GraphQL error `extensions` keys to copy onto the span event.
6+
* @param {import('../../dd-trace/src/opentracing/span')} span
7+
* @param {{ name?: string, message?: string, stack?: string, locations?: Array<{ line: number, column: number }>,
8+
* path?: Array<string|number>, extensions?: Record<string, unknown> }} exc
9+
*/
310
function extractErrorIntoSpanEvent (config, span, exc) {
411
const attributes = {}
512

@@ -29,8 +36,8 @@ function extractErrorIntoSpanEvent (config, span, exc) {
2936
attributes.message = exc.message
3037
}
3138

32-
if (config.DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) {
33-
for (const ext of config.DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) {
39+
if (config.errorExtensions) {
40+
for (const ext of config.errorExtensions) {
3441
if (exc.extensions?.[ext]) {
3542
const value = exc.extensions[ext]
3643

packages/datadog-plugin-graphql/src/validate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class GraphQLValidatePlugin extends TracingPlugin {
3838
if (errors?.length) {
3939
span.setTag('error', errors[0])
4040
for (const err of errors) {
41-
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
41+
extractErrorIntoSpanEvent(this.config, span, err)
4242
}
4343
}
4444

packages/datadog-plugin-graphql/test/index.spec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,64 @@ describe('Plugin', () => {
20372037
})
20382038
})
20392039

2040+
describe('with configured error extensions', () => {
2041+
before(() => {
2042+
tracer = require('../../dd-trace')
2043+
2044+
return agent.load('graphql', { errorExtensions: ['code', 'extra'] })
2045+
})
2046+
2047+
after(() => {
2048+
return agent.close()
2049+
})
2050+
2051+
beforeEach(() => {
2052+
graphql = require(`../../../versions/graphql@${version}`).get()
2053+
buildSchema()
2054+
})
2055+
2056+
it('traces with the configured extensions resolved', () => {
2057+
const source = '{ hello(name: "world") }'
2058+
2059+
const assertion = agent.assertSomeTraces(traces => {
2060+
const spans = sort(traces[0])
2061+
2062+
assert.strictEqual(spans[0].name, expectedSchema.server.opName)
2063+
})
2064+
2065+
return Promise.all([assertion, graphql.graphql({ schema, source })])
2066+
})
2067+
})
2068+
2069+
describe('with invalid configuration', () => {
2070+
before(() => {
2071+
tracer = require('../../dd-trace')
2072+
2073+
return agent.load('graphql', { depth: 'all', variables: 5, errorExtensions: 'code' })
2074+
})
2075+
2076+
after(() => {
2077+
return agent.close()
2078+
})
2079+
2080+
beforeEach(() => {
2081+
graphql = require(`../../../versions/graphql@${version}`).get()
2082+
buildSchema()
2083+
})
2084+
2085+
it('falls back to defaults and still traces', () => {
2086+
const source = '{ hello(name: "world") }'
2087+
2088+
const assertion = agent.assertSomeTraces(traces => {
2089+
const spans = sort(traces[0])
2090+
2091+
assert.strictEqual(spans[0].name, expectedSchema.server.opName)
2092+
})
2093+
2094+
return Promise.all([assertion, graphql.graphql({ schema, source })])
2095+
})
2096+
})
2097+
20402098
describe('with a depth of 0', () => {
20412099
before(() => {
20422100
tracer = require('../../dd-trace')

packages/datadog-plugin-graphql/test/tools/signature.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ describe('graphql signature fallback', () => {
224224
}
225225

226226
extractErrorIntoSpanEvent({
227-
DD_TRACE_GRAPHQL_ERROR_EXTENSIONS: ['code', 'retryable', 'detail', 'missing'],
227+
errorExtensions: ['code', 'retryable', 'detail', 'missing'],
228228
}, span, error)
229229

230230
const [name, attributes] = span.addEvent.firstCall.args

packages/dd-trace/src/config/generated-config-types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,14 @@ export interface GeneratedConfig {
253253
DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED: boolean;
254254
DD_TRACE_GOOGLE_GAX_ENABLED: boolean;
255255
DD_TRACE_GOOGLE_GENAI_ENABLED: boolean;
256+
DD_TRACE_GRAPHQL_COLLAPSE: boolean;
257+
DD_TRACE_GRAPHQL_DEPTH: number;
256258
DD_TRACE_GRAPHQL_ENABLED: boolean;
257259
DD_TRACE_GRAPHQL_ERROR_EXTENSIONS: string[];
258260
DD_TRACE_GRAPHQL_TAG_ENABLED: boolean;
259261
DD_TRACE_GRAPHQL_TOOLS_ENABLED: boolean;
260262
DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED: boolean;
263+
DD_TRACE_GRAPHQL_VARIABLES: string[];
261264
DD_TRACE_GRAPHQL_YOGA_ENABLED: boolean;
262265
DD_TRACE_GRPC_ENABLED: boolean;
263266
DD_TRACE_GRPC_GRPC_JS_ENABLED: boolean;
@@ -921,11 +924,14 @@ export interface GeneratedEnvVarConfig {
921924
DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED: boolean;
922925
DD_TRACE_GOOGLE_GAX_ENABLED: boolean;
923926
DD_TRACE_GOOGLE_GENAI_ENABLED: boolean;
927+
DD_TRACE_GRAPHQL_COLLAPSE: boolean;
928+
DD_TRACE_GRAPHQL_DEPTH: number;
924929
DD_TRACE_GRAPHQL_ENABLED: boolean;
925930
DD_TRACE_GRAPHQL_ERROR_EXTENSIONS: string[];
926931
DD_TRACE_GRAPHQL_TAG_ENABLED: boolean;
927932
DD_TRACE_GRAPHQL_TOOLS_ENABLED: boolean;
928933
DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED: boolean;
934+
DD_TRACE_GRAPHQL_VARIABLES: string[];
929935
DD_TRACE_GRAPHQL_YOGA_ENABLED: boolean;
930936
DD_TRACE_GRPC_ENABLED: boolean;
931937
DD_TRACE_GRPC_GRPC_JS_ENABLED: boolean;

packages/dd-trace/src/config/supported-configurations.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2757,6 +2757,21 @@
27572757
"default": "true"
27582758
}
27592759
],
2760+
"DD_TRACE_GRAPHQL_COLLAPSE": [
2761+
{
2762+
"implementation": "A",
2763+
"type": "boolean",
2764+
"default": "true"
2765+
}
2766+
],
2767+
"DD_TRACE_GRAPHQL_DEPTH": [
2768+
{
2769+
"implementation": "A",
2770+
"type": "int",
2771+
"default": "-1",
2772+
"allowed": "-1|\\d+"
2773+
}
2774+
],
27602775
"DD_TRACE_GRAPHQL_ENABLED": [
27612776
{
27622777
"implementation": "A",
@@ -2792,6 +2807,13 @@
27922807
"default": "true"
27932808
}
27942809
],
2810+
"DD_TRACE_GRAPHQL_VARIABLES": [
2811+
{
2812+
"implementation": "A",
2813+
"type": "array",
2814+
"default": ""
2815+
}
2816+
],
27952817
"DD_TRACE_GRAPHQL_YOGA_ENABLED": [
27962818
{
27972819
"implementation": "A",

0 commit comments

Comments
 (0)