Skip to content

Commit 3ea8eb2

Browse files
wconti27watson
authored andcommitted
chore(tracing): graphql error support (#5162)
* add graphql error reporting via span links
1 parent 175f12b commit 3ea8eb2

File tree

6 files changed

+104
-1
lines changed

6 files changed

+104
-1
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4+
const { extractErrorIntoSpanEvent } = require('./utils')
45

56
let tools
67

@@ -34,6 +35,11 @@ class GraphQLExecutePlugin extends TracingPlugin {
3435
finish ({ res, args }) {
3536
const span = this.activeSpan
3637
this.config.hooks.execute(span, args, res)
38+
if (res?.errors) {
39+
for (const err of res.errors) {
40+
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
41+
}
42+
}
3743
super.finish()
3844
}
3945
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function extractErrorIntoSpanEvent (config, span, exc) {
2+
const attributes = {}
3+
4+
if (exc.name) {
5+
attributes.type = exc.name
6+
}
7+
8+
if (exc.stack) {
9+
attributes.stacktrace = exc.stack
10+
}
11+
12+
if (exc.locations) {
13+
attributes.locations = []
14+
for (const location of exc.locations) {
15+
attributes.locations.push(`${location.line}:${location.column}`)
16+
}
17+
}
18+
19+
if (exc.path) {
20+
attributes.path = exc.path.map(String)
21+
}
22+
23+
if (exc.message) {
24+
attributes.message = exc.message
25+
}
26+
27+
if (config.graphqlErrorExtensions) {
28+
for (const ext of config.graphqlErrorExtensions) {
29+
if (exc.extensions?.[ext]) {
30+
attributes[`extensions.${ext}`] = exc.extensions[ext].toString()
31+
}
32+
}
33+
}
34+
35+
span.addEvent('dd.graphql.query.error', attributes, Date.now())
36+
}
37+
38+
module.exports = {
39+
extractErrorIntoSpanEvent
40+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4+
const { extractErrorIntoSpanEvent } = require('./utils')
45

56
class GraphQLValidatePlugin extends TracingPlugin {
67
static get id () { return 'graphql' }
@@ -21,6 +22,11 @@ class GraphQLValidatePlugin extends TracingPlugin {
2122
finish ({ document, errors }) {
2223
const span = this.activeSpan
2324
this.config.hooks.validate(span, document, errors)
25+
if (errors) {
26+
for (const err of errors) {
27+
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
28+
}
29+
}
2430
super.finish()
2531
}
2632
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,18 @@ describe('Plugin', () => {
920920
expect(spans[0].meta).to.have.property(ERROR_MESSAGE, errors[0].message)
921921
expect(spans[0].meta).to.have.property(ERROR_STACK, errors[0].stack)
922922
expect(spans[0].meta).to.have.property('component', 'graphql')
923+
924+
const spanEvents = agent.unformatSpanEvents(spans[0])
925+
926+
expect(spanEvents).to.have.length(1)
927+
expect(spanEvents[0]).to.have.property('startTime')
928+
expect(spanEvents[0]).to.have.property('name', 'dd.graphql.query.error')
929+
expect(spanEvents[0].attributes).to.have.property('type', 'GraphQLError')
930+
expect(spanEvents[0].attributes).to.have.property('stacktrace')
931+
expect(spanEvents[0].attributes).to.have.property('message', 'Field "address" of ' +
932+
'type "Address" must have a selection of subfields. Did you mean "address { ... }"?')
933+
expect(spanEvents[0].attributes.locations).to.have.length(1)
934+
expect(spanEvents[0].attributes.locations[0]).to.equal('1:11')
923935
})
924936
.then(done)
925937
.catch(done)
@@ -986,6 +998,19 @@ describe('Plugin', () => {
986998
expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message)
987999
expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack)
9881000
expect(spans[0].meta).to.have.property('component', 'graphql')
1001+
1002+
const spanEvents = agent.unformatSpanEvents(spans[0])
1003+
1004+
expect(spanEvents).to.have.length(1)
1005+
expect(spanEvents[0]).to.have.property('startTime')
1006+
expect(spanEvents[0]).to.have.property('name', 'dd.graphql.query.error')
1007+
expect(spanEvents[0].attributes).to.have.property('type', 'GraphQLError')
1008+
expect(spanEvents[0].attributes).to.have.property('stacktrace')
1009+
expect(spanEvents[0].attributes).to.have.property('message', 'test')
1010+
expect(spanEvents[0].attributes.locations).to.have.length(1)
1011+
expect(spanEvents[0].attributes.locations[0]).to.equal('1:3')
1012+
expect(spanEvents[0].attributes.path).to.have.length(1)
1013+
expect(spanEvents[0].attributes.path[0]).to.equal('hello')
9891014
})
9901015
.then(done)
9911016
.catch(done)

packages/dd-trace/src/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ class Config {
482482
this._setValue(defaults, 'flushInterval', 2000)
483483
this._setValue(defaults, 'flushMinSpans', 1000)
484484
this._setValue(defaults, 'gitMetadataEnabled', true)
485+
this._setValue(defaults, 'graphqlErrorExtensions', [])
485486
this._setValue(defaults, 'grpc.client.error.statuses', GRPC_CLIENT_ERROR_STATUSES)
486487
this._setValue(defaults, 'grpc.server.error.statuses', GRPC_SERVER_ERROR_STATUSES)
487488
this._setValue(defaults, 'headerTags', [])
@@ -669,6 +670,7 @@ class Config {
669670
DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED,
670671
DD_TRACE_GIT_METADATA_ENABLED,
671672
DD_TRACE_GLOBAL_TAGS,
673+
DD_TRACE_GRAPHQL_ERROR_EXTENSIONS,
672674
DD_TRACE_HEADER_TAGS,
673675
DD_TRACE_LEGACY_BAGGAGE_ENABLED,
674676
DD_TRACE_MEMCACHED_COMMAND_ENABLED,
@@ -895,6 +897,7 @@ class Config {
895897
this._setString(env, 'version', DD_VERSION || tags.version)
896898
this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
897899
this._setString(env, 'aws.dynamoDb.tablePrimaryKeys', DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS)
900+
this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
898901
}
899902

900903
_applyOptions (options) {
@@ -1020,6 +1023,7 @@ class Config {
10201023
this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
10211024
this._setString(opts, 'version', options.version || tags.version)
10221025
this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
1026+
this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
10231027

10241028
// For LLMObs, we want the environment variable to take precedence over the options.
10251029
// This is reliant on environment config being set before options.

packages/dd-trace/test/plugins/agent.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,27 @@ function dsmStatsExistWithParentHash (agent, expectedParentHash) {
8787
return hashFound
8888
}
8989

90+
function unformatSpanEvents (span) {
91+
if (span.meta && span.meta.events) {
92+
// Parse the JSON string back into an object
93+
const events = JSON.parse(span.meta.events)
94+
95+
// Create the _events array
96+
const spanEvents = events.map(event => {
97+
return {
98+
name: event.name,
99+
startTime: event.time_unix_nano / 1e6, // Convert from nanoseconds back to milliseconds
100+
attributes: event.attributes ? event.attributes : undefined
101+
}
102+
})
103+
104+
// Return the unformatted _events
105+
return spanEvents
106+
}
107+
108+
return [] // Return an empty array if no events are found
109+
}
110+
90111
function addEnvironmentVariablesToHeaders (headers) {
91112
// get all environment variables that start with "DD_"
92113
const ddEnvVars = new Map(
@@ -443,5 +464,6 @@ module.exports = {
443464
testedPlugins,
444465
getDsmStats,
445466
dsmStatsExist,
446-
dsmStatsExistWithParentHash
467+
dsmStatsExistWithParentHash,
468+
unformatSpanEvents
447469
}

0 commit comments

Comments
 (0)