dd-trace
Version:
Datadog APM tracing client for JavaScript
87 lines (69 loc) • 2.93 kB
JavaScript
const { storage } = require('../../datadog-core')
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
const GraphQLParsePlugin = require('./parse')
const { extractErrorIntoSpanEvent, isApolloHealthCheck, refineRequestSpan } = require('./utils')
const legacyStorage = storage('legacy')
class GraphQLValidatePlugin extends TracingPlugin {
static id = 'graphql'
static operation = 'validate'
static prefix = 'tracing:orchestrion:graphql:apm:graphql:validate'
bindStart (ctx) {
// validate(schema, documentAST, rules, options, typeInfo)
const document = ctx.arguments?.[1]
// Verify the marked document in case the caller transformed its AST after parsing.
if (document &&
GraphQLParsePlugin.healthCheckDocuments.has(document) &&
document.definitions?.length === 1 &&
isApolloHealthCheck(document.definitions[0])) {
ctx.ddSkipped = true
return ctx.currentStore
}
const docSource = document ? GraphQLParsePlugin.documentSources.get(document) : undefined
const source = this.config.source && document && docSource
// Refine the top-level graphql.request span (mercurius) from the parsed
// document. validate is the first boundary that has it and precedes both
// execute and any pre-execute rejection (unknown field, GET mutation), so a
// failing request still ends up with a resource and operation tags. The
// request span, its operation name, and the raw source ride the active
// store the request boundary entered (validate's own `ctx.currentStore` is
// not populated yet). The cache is keyed by that raw source, not the parsed
// document — for a pre-parsed AST mercurius validates a structuredClone, so
// the document here is a different object from the one the boundary saw and
// recovers on the warm path. No-op for graphql-js/apollo/yoga, which never
// open a request span.
const requestStore = legacyStorage.getStore()
refineRequestSpan(
requestStore?.graphqlRequestSpan,
document,
requestStore?.graphqlRequestSource,
requestStore?.graphqlRequestOperationName,
this.config.signature
)
this.startSpan('graphql.validate', {
service: this.config.service,
type: 'graphql',
meta: {
'graphql.source': source,
},
}, ctx)
ctx.ddDocument = document
return ctx.currentStore
}
end (ctx) {
if (ctx.ddSkipped) return ctx.parentStore
const document = ctx.ddDocument
const errors = ctx.result
const span = ctx?.currentStore?.span || this.activeSpan
if (errors?.length) {
span.setTag('error', errors[0])
for (const err of errors) {
extractErrorIntoSpanEvent(this.config, span, err)
}
}
this.config.hooks.validate(span, document, errors)
span.finish()
return ctx.parentStore
}
}
module.exports = GraphQLValidatePlugin