dd-trace 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.electron.js +3 -0
- package/package.json +6 -2
- package/packages/datadog-instrumentations/src/ai.js +29 -24
- package/packages/datadog-instrumentations/src/cucumber.js +4 -3
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +10 -4
- package/packages/datadog-instrumentations/src/router.js +180 -105
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
- package/packages/datadog-plugin-graphql/src/execute.js +90 -14
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +32 -1
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +13 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/supported-configurations.json +17 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/feature-registry.js +22 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/tagger.js +11 -7
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/register.js +46 -0
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/util/test.js +9 -4
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +21 -9
- package/packages/dd-trace/src/standalone/index.js +0 -22
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
|
@@ -5,7 +5,7 @@ const dc = require('dc-polyfill')
|
|
|
5
5
|
const { storage } = require('../../datadog-core')
|
|
6
6
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
7
7
|
const GraphQLParsePlugin = require('./parse')
|
|
8
|
-
const { extractErrorIntoSpanEvent, getOperation, getSignature } = require('./utils')
|
|
8
|
+
const { extractErrorIntoSpanEvent, getOperation, getSignature, isApolloHealthCheck } = require('./utils')
|
|
9
9
|
|
|
10
10
|
const legacyStorage = storage('legacy')
|
|
11
11
|
|
|
@@ -26,12 +26,24 @@ const contexts = new WeakMap()
|
|
|
26
26
|
const instrumentedArgs = new WeakSet()
|
|
27
27
|
|
|
28
28
|
const patchedResolvers = new WeakSet()
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
// Visited types per caller-owned schema. The walk reaches union members and
|
|
31
|
+
// interface implementations through the schema (`getTypes`/`getPossibleTypes`),
|
|
32
|
+
// so it differs per schema: a global guard would stop the second schema at any
|
|
33
|
+
// type the first already walked and leave its own implementations unwrapped.
|
|
34
|
+
// `patchedResolvers` keeps wrapping idempotent, so re-walking a shared type is
|
|
35
|
+
// safe and this set only terminates cycles.
|
|
36
|
+
const walkedTypes = new WeakMap()
|
|
30
37
|
|
|
31
38
|
// Module-level fast path: skip the resolver-side WeakMap lookup entirely
|
|
32
39
|
// when depth=0 disables resolver instrumentation.
|
|
33
40
|
let depthDisabled = false
|
|
34
41
|
|
|
42
|
+
// Initial key for the per-operation variables-filter cache. A unique sentinel
|
|
43
|
+
// so the first #filterVariables call never falsely matches, even when the
|
|
44
|
+
// operation's variableValues is undefined.
|
|
45
|
+
const NO_VARIABLES_CACHED = Symbol('noVariablesCached')
|
|
46
|
+
|
|
35
47
|
class AbortError extends Error {
|
|
36
48
|
constructor (message) {
|
|
37
49
|
super(message)
|
|
@@ -111,6 +123,16 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
111
123
|
const name = operation?.name?.value
|
|
112
124
|
const source = this.config.source && docSource
|
|
113
125
|
|
|
126
|
+
// Apollo Server may execute a cached document without parsing it first.
|
|
127
|
+
// Match the full gateway operation here so caller-owned AST transformations
|
|
128
|
+
// cannot suppress execute/resolver AppSec and IAST channels.
|
|
129
|
+
if (name === '__ApolloServiceHealthCheck__' &&
|
|
130
|
+
document.definitions.length === 1 &&
|
|
131
|
+
isApolloHealthCheck(operation)) {
|
|
132
|
+
ctx.ddSkipped = true
|
|
133
|
+
return ctx.currentStore
|
|
134
|
+
}
|
|
135
|
+
|
|
114
136
|
ctx.collapse = this.config.collapse
|
|
115
137
|
|
|
116
138
|
const signature = getSignature(document, name, type, this.config.signature)
|
|
@@ -165,9 +187,9 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
165
187
|
|
|
166
188
|
const schema = args.schema
|
|
167
189
|
if (schema) {
|
|
168
|
-
wrapFields(schema._queryType)
|
|
169
|
-
wrapFields(schema._mutationType)
|
|
170
|
-
wrapFields(schema._subscriptionType)
|
|
190
|
+
wrapFields(schema._queryType, schema)
|
|
191
|
+
wrapFields(schema._mutationType, schema)
|
|
192
|
+
wrapFields(schema._subscriptionType, schema)
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
const rootCtx = {
|
|
@@ -179,6 +201,10 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
179
201
|
abortController,
|
|
180
202
|
executeSpan: span,
|
|
181
203
|
plugin: this,
|
|
204
|
+
// graphql.resolve variable tags: memoize the filtered result for the
|
|
205
|
+
// last-seen variableValues object (see #filterVariables).
|
|
206
|
+
filteredVariablesKey: NO_VARIABLES_CACHED,
|
|
207
|
+
filteredVariables: undefined,
|
|
182
208
|
}
|
|
183
209
|
ctx.ddRootCtx = rootCtx
|
|
184
210
|
if (isWeakMapKey(contextValue)) {
|
|
@@ -291,7 +317,7 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
291
317
|
field.span = span
|
|
292
318
|
|
|
293
319
|
if (fieldNode && this.config.variables && fieldNode.arguments) {
|
|
294
|
-
const variables = this
|
|
320
|
+
const variables = this.#filterVariables(rootCtx, variableValues)
|
|
295
321
|
for (const arg of fieldNode.arguments) {
|
|
296
322
|
if (arg.value?.name && arg.value.kind === 'Variable' && variables[arg.value.name.value]) {
|
|
297
323
|
const name = arg.value.name.value
|
|
@@ -303,6 +329,27 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
303
329
|
return span
|
|
304
330
|
}
|
|
305
331
|
|
|
332
|
+
// Memoize the user variables filter against the last-seen variableValues
|
|
333
|
+
// object. graphql hands every resolver in one execute the same coerced
|
|
334
|
+
// variableValues object, so all arg-bearing fields hit the identity fast
|
|
335
|
+
// path and the filter runs once per operation. A nested execute() sharing
|
|
336
|
+
// the same object contextValue reuses the outer rootCtx but carries its own
|
|
337
|
+
// variableValues; comparing by identity recomputes for it (and any later
|
|
338
|
+
// fields on that inner object reuse the slot), so each field's tags stay
|
|
339
|
+
// correct. A single slot beats a WeakMap here: no per-operation allocation,
|
|
340
|
+
// and the common single-object case is a bare `===` (see the microbenchmark
|
|
341
|
+
// numbers in the commit body).
|
|
342
|
+
#filterVariables (rootCtx, variableValues) {
|
|
343
|
+
if (rootCtx.filteredVariablesKey === variableValues) {
|
|
344
|
+
return rootCtx.filteredVariables
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const filtered = this.config.variables(variableValues)
|
|
348
|
+
rootCtx.filteredVariablesKey = variableValues
|
|
349
|
+
rootCtx.filteredVariables = filtered
|
|
350
|
+
return filtered
|
|
351
|
+
}
|
|
352
|
+
|
|
306
353
|
// Public — called from wrapResolve. endTime reflects when the resolver
|
|
307
354
|
// actually completed, not when the field record was created.
|
|
308
355
|
finishResolveSpan (span, field, error, result, endTime) {
|
|
@@ -432,15 +479,44 @@ function wrapResolve (resolve) {
|
|
|
432
479
|
return resolveAsync
|
|
433
480
|
}
|
|
434
481
|
|
|
435
|
-
function wrapFields (type) {
|
|
436
|
-
if (!type
|
|
482
|
+
function wrapFields (type, schema) {
|
|
483
|
+
if (!type || !markWalked(schema, type)) return
|
|
484
|
+
|
|
485
|
+
const tag = type[Symbol.toStringTag]
|
|
437
486
|
|
|
438
|
-
|
|
487
|
+
// Union types (e.g. Apollo Federation's `_Entity`) hold their members on
|
|
488
|
+
// `_types`, not `_fields`. Their member object types are reachable only here,
|
|
489
|
+
// so descend into each to wrap the entity resolvers a `_entities` query runs.
|
|
490
|
+
if (tag === 'GraphQLUnionType') {
|
|
491
|
+
for (const member of type.getTypes()) wrapFields(member, schema)
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (type._fields) {
|
|
496
|
+
for (const field of Object.values(type._fields)) {
|
|
497
|
+
wrapFieldResolve(field)
|
|
498
|
+
wrapFieldType(field, schema)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Interface implementations carry their own resolvers and are reachable only
|
|
503
|
+
// through `getPossibleTypes`; an interface return type alone never wraps them.
|
|
504
|
+
if (schema && tag === 'GraphQLInterfaceType') {
|
|
505
|
+
for (const impl of schema.getPossibleTypes(type)) wrapFields(impl, schema)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
439
508
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
509
|
+
// Marks the guard on entry so recursive types (a field looping back to its own
|
|
510
|
+
// type, an interface an implementation returns) terminate the walk.
|
|
511
|
+
function markWalked (schema, type) {
|
|
512
|
+
let walked = walkedTypes.get(schema)
|
|
513
|
+
if (walked === undefined) {
|
|
514
|
+
walked = new WeakSet()
|
|
515
|
+
walkedTypes.set(schema, walked)
|
|
443
516
|
}
|
|
517
|
+
if (walked.has(type)) return false
|
|
518
|
+
walked.add(type)
|
|
519
|
+
return true
|
|
444
520
|
}
|
|
445
521
|
|
|
446
522
|
function wrapFieldResolve (field) {
|
|
@@ -448,13 +524,13 @@ function wrapFieldResolve (field) {
|
|
|
448
524
|
field.resolve = wrapResolve(field.resolve)
|
|
449
525
|
}
|
|
450
526
|
|
|
451
|
-
function wrapFieldType (field) {
|
|
527
|
+
function wrapFieldType (field, schema) {
|
|
452
528
|
if (!field?.type) return
|
|
453
529
|
|
|
454
530
|
let unwrapped = field.type
|
|
455
531
|
while (unwrapped.ofType) unwrapped = unwrapped.ofType
|
|
456
532
|
|
|
457
|
-
wrapFields(unwrapped)
|
|
533
|
+
wrapFields(unwrapped, schema)
|
|
458
534
|
}
|
|
459
535
|
|
|
460
536
|
// Runs the resolver inside `store`, including any code after an internal
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const { isApolloHealthCheckSource } = require('./utils')
|
|
4
5
|
|
|
5
6
|
const documentSources = new WeakMap()
|
|
6
7
|
|
|
8
|
+
// Documents produced by parsing an Apollo Gateway health-check poll. Populated
|
|
9
|
+
// here (parse owns the document lifecycle, like documentSources) and read by the
|
|
10
|
+
// validate plugin. Execute independently verifies the operation for cached docs.
|
|
11
|
+
const healthCheckDocuments = new WeakSet()
|
|
12
|
+
|
|
7
13
|
class GraphQLParsePlugin extends TracingPlugin {
|
|
8
14
|
static id = 'graphql'
|
|
9
15
|
static operation = 'parser'
|
|
@@ -12,6 +18,14 @@ class GraphQLParsePlugin extends TracingPlugin {
|
|
|
12
18
|
bindStart (ctx) {
|
|
13
19
|
const source = ctx.arguments?.[0]
|
|
14
20
|
|
|
21
|
+
// Apollo Gateway polls every subgraph with a fixed health-check query.
|
|
22
|
+
// Mark its document after parsing so validation can skip the same poll.
|
|
23
|
+
if (isApolloHealthCheckSource(source?.body ?? source)) {
|
|
24
|
+
ctx.ddHealthCheck = true
|
|
25
|
+
ctx.ddSkipped = true
|
|
26
|
+
return ctx.currentStore
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
this.startSpan('graphql.parse', {
|
|
16
30
|
service: this.config.service,
|
|
17
31
|
type: 'graphql',
|
|
@@ -24,8 +38,14 @@ class GraphQLParsePlugin extends TracingPlugin {
|
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
end (ctx) {
|
|
27
|
-
const source = ctx.ddSource
|
|
28
41
|
const document = ctx.result
|
|
42
|
+
|
|
43
|
+
if (ctx.ddHealthCheck) {
|
|
44
|
+
if (document) healthCheckDocuments.add(document)
|
|
45
|
+
return ctx.parentStore
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const source = ctx.ddSource
|
|
29
49
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
30
50
|
|
|
31
51
|
let docSource
|
|
@@ -51,5 +71,6 @@ class GraphQLParsePlugin extends TracingPlugin {
|
|
|
51
71
|
}
|
|
52
72
|
|
|
53
73
|
GraphQLParsePlugin.documentSources = documentSources
|
|
74
|
+
GraphQLParsePlugin.healthCheckDocuments = healthCheckDocuments
|
|
54
75
|
|
|
55
76
|
module.exports = GraphQLParsePlugin
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
-
const { extractErrorIntoSpanEvent, getCachedRequestOperation } = require('./utils')
|
|
4
|
+
const { extractErrorIntoSpanEvent, getCachedRequestOperation, isApolloHealthCheckSource } = require('./utils')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} GraphQLRequestStore
|
|
8
|
+
* @property {import('../../dd-trace/src/opentracing/span')} [span]
|
|
9
|
+
* @property {import('../../dd-trace/src/opentracing/span')} [graphqlRequestSpan]
|
|
10
|
+
* @property {string} [graphqlRequestOperationName]
|
|
11
|
+
* @property {unknown} [graphqlRequestSource]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} GraphQLRequestContext
|
|
16
|
+
* @property {unknown[]} [arguments]
|
|
17
|
+
* @property {GraphQLRequestStore} [currentStore]
|
|
18
|
+
* @property {GraphQLRequestStore} [parentStore]
|
|
19
|
+
* @property {boolean} [ddSkipped]
|
|
20
|
+
* @property {{ errors?: import('graphql').GraphQLError[] }} [result]
|
|
21
|
+
*/
|
|
5
22
|
|
|
6
23
|
// Top-level GraphQL request span for drivers that funnel every operation
|
|
7
24
|
// through a single entry point but parse/validate/execute internally (mercurius
|
|
@@ -25,9 +42,18 @@ class GraphQLRequestPlugin extends TracingPlugin {
|
|
|
25
42
|
static kind = 'server'
|
|
26
43
|
static prefix = 'tracing:orchestrion:mercurius:apm:graphql:request'
|
|
27
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @param {GraphQLRequestContext} ctx
|
|
47
|
+
*/
|
|
28
48
|
bindStart (ctx) {
|
|
29
49
|
// fastifyGraphQl(source, context, variables, operationName)
|
|
30
50
|
const source = ctx.arguments?.[0]
|
|
51
|
+
|
|
52
|
+
if (isApolloHealthCheckSource(source)) {
|
|
53
|
+
ctx.ddSkipped = true
|
|
54
|
+
return ctx.currentStore
|
|
55
|
+
}
|
|
56
|
+
|
|
31
57
|
const operationName = ctx.arguments?.[3]
|
|
32
58
|
|
|
33
59
|
// `source` is the request text on the common path, but mercurius also
|
|
@@ -72,7 +98,12 @@ class GraphQLRequestPlugin extends TracingPlugin {
|
|
|
72
98
|
return ctx.currentStore
|
|
73
99
|
}
|
|
74
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @param {GraphQLRequestContext} ctx
|
|
103
|
+
*/
|
|
75
104
|
asyncEnd (ctx) {
|
|
105
|
+
if (ctx.ddSkipped) return ctx.parentStore
|
|
106
|
+
|
|
76
107
|
/* istanbul ignore next: currentStore is populated for the request lifecycle; activeSpan is base-plugin fallback. */
|
|
77
108
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
78
109
|
/* istanbul ignore if: startSpan always populates currentStore for the request lifecycle. */
|
|
@@ -228,6 +228,46 @@ function extractErrorIntoSpanEvent (config, span, exc) {
|
|
|
228
228
|
span.addEvent('dd.graphql.query.error', attributes, Date.now())
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// Apollo Gateway's fixed subgraph health-check query, sent verbatim on every
|
|
232
|
+
// poll interval. See https://github.com/apollographql/federation
|
|
233
|
+
// `HEALTH_CHECK_QUERY`.
|
|
234
|
+
const HEALTH_CHECK_QUERY = 'query __ApolloServiceHealthCheck__ { __typename }'
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Matches the raw query string before it is parsed (the only input parse has).
|
|
238
|
+
*
|
|
239
|
+
* @param {unknown} source Raw query string or a graphql `Source` body.
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
function isApolloHealthCheckSource (source) {
|
|
243
|
+
return source === HEALTH_CHECK_QUERY
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Matches Apollo's parsed health-check operation exactly for cached documents.
|
|
248
|
+
*
|
|
249
|
+
* @param {import('graphql').OperationDefinitionNode | undefined} operation
|
|
250
|
+
* @returns {boolean}
|
|
251
|
+
*/
|
|
252
|
+
function isApolloHealthCheck (operation) {
|
|
253
|
+
const selections = operation?.selectionSet?.selections
|
|
254
|
+
if (operation?.operation !== 'query' ||
|
|
255
|
+
operation.name?.value !== '__ApolloServiceHealthCheck__' ||
|
|
256
|
+
operation.variableDefinitions?.length ||
|
|
257
|
+
operation.directives?.length ||
|
|
258
|
+
selections?.length !== 1) {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const selection = selections[0]
|
|
263
|
+
return selection.kind === 'Field' &&
|
|
264
|
+
selection.name?.value === '__typename' &&
|
|
265
|
+
selection.alias === undefined &&
|
|
266
|
+
selection.selectionSet === undefined &&
|
|
267
|
+
selection.arguments?.length === 0 &&
|
|
268
|
+
selection.directives?.length === 0
|
|
269
|
+
}
|
|
270
|
+
|
|
231
271
|
let tools
|
|
232
272
|
|
|
233
273
|
function getSignature (document, operationName, operationType, calculate) {
|
|
@@ -261,5 +301,7 @@ module.exports = {
|
|
|
261
301
|
getCachedRequestOperation,
|
|
262
302
|
getOperation,
|
|
263
303
|
getSignature,
|
|
304
|
+
isApolloHealthCheck,
|
|
305
|
+
isApolloHealthCheckSource,
|
|
264
306
|
refineRequestSpan,
|
|
265
307
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { storage } = require('../../datadog-core')
|
|
4
4
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
5
5
|
const GraphQLParsePlugin = require('./parse')
|
|
6
|
-
const { extractErrorIntoSpanEvent, refineRequestSpan } = require('./utils')
|
|
6
|
+
const { extractErrorIntoSpanEvent, isApolloHealthCheck, refineRequestSpan } = require('./utils')
|
|
7
7
|
|
|
8
8
|
const legacyStorage = storage('legacy')
|
|
9
9
|
|
|
@@ -15,6 +15,16 @@ class GraphQLValidatePlugin extends TracingPlugin {
|
|
|
15
15
|
bindStart (ctx) {
|
|
16
16
|
// validate(schema, documentAST, rules, options, typeInfo)
|
|
17
17
|
const document = ctx.arguments?.[1]
|
|
18
|
+
|
|
19
|
+
// Verify the marked document in case the caller transformed its AST after parsing.
|
|
20
|
+
if (document &&
|
|
21
|
+
GraphQLParsePlugin.healthCheckDocuments.has(document) &&
|
|
22
|
+
document.definitions?.length === 1 &&
|
|
23
|
+
isApolloHealthCheck(document.definitions[0])) {
|
|
24
|
+
ctx.ddSkipped = true
|
|
25
|
+
return ctx.currentStore
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
const docSource = document ? GraphQLParsePlugin.documentSources.get(document) : undefined
|
|
19
29
|
const source = this.config.source && document && docSource
|
|
20
30
|
|
|
@@ -52,6 +62,8 @@ class GraphQLValidatePlugin extends TracingPlugin {
|
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
end (ctx) {
|
|
65
|
+
if (ctx.ddSkipped) return ctx.parentStore
|
|
66
|
+
|
|
55
67
|
const document = ctx.ddDocument
|
|
56
68
|
const errors = ctx.result
|
|
57
69
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
// Plugin temporarily disabled. See https://github.com/DataDog/dd-trace-js/issues/312
|
|
4
|
-
|
|
5
3
|
const ServerPlugin = require('../../dd-trace/src/plugins/server')
|
|
6
4
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
7
5
|
const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
|
|
@@ -10,6 +8,7 @@ class Http2ServerPlugin extends ServerPlugin {
|
|
|
10
8
|
constructor (tracer, config) {
|
|
11
9
|
super(tracer, config)
|
|
12
10
|
this.addBind('apm:http2:server:response:emit', this.bindEmit)
|
|
11
|
+
this.addSub('apm:http2:server:request:adopt', this.adopt)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
static id = 'http2'
|
|
@@ -47,15 +46,35 @@ class Http2ServerPlugin extends ServerPlugin {
|
|
|
47
46
|
|
|
48
47
|
const context = web.getContext(req)
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// A mixed server adopts the real request off this stream later; key the
|
|
50
|
+
// context on the stream now so that lookup resolves. Skipped for the common
|
|
51
|
+
// single-listener request, which never adopts.
|
|
52
|
+
if (ctx.adoptable) web.linkContextToStream(req.stream, context)
|
|
53
|
+
|
|
54
|
+
instrumentWriteHead(context)
|
|
54
55
|
|
|
55
56
|
return ctx.currentStore
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// A mixed server (raw-stream + 'request' listeners) creates the span from the
|
|
60
|
+
// 'stream' event with a throwaway adapter. When the compatibility layer then
|
|
61
|
+
// synthesizes the real request/response off the same stream, point the shared
|
|
62
|
+
// context at them so `web.setFramework`/`web.setRoute` from the user's
|
|
63
|
+
// 'request' handler resolve to this span and the finish `hooks.request`
|
|
64
|
+
// receives the real objects instead of the adapter.
|
|
65
|
+
adopt (ctx) {
|
|
66
|
+
const context = web.patch(ctx.req)
|
|
67
|
+
context.req = ctx.req
|
|
68
|
+
context.res = ctx.res
|
|
69
|
+
instrumentWriteHead(context)
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
bindEmit (ctx) {
|
|
73
|
+
// Both the compatibility response and the core-API stream emit 'close'
|
|
74
|
+
// exactly once, so the span is finished from a single source. `web.js`
|
|
75
|
+
// bypasses its `finished` idempotency guard for stream-backed requests
|
|
76
|
+
// (`!req.stream`); that bypass is harmless here only because of this
|
|
77
|
+
// single-finish property.
|
|
59
78
|
if (ctx.eventName !== 'close') return ctx.currentStore
|
|
60
79
|
|
|
61
80
|
const { req } = ctx
|
|
@@ -78,4 +97,17 @@ class Http2ServerPlugin extends ServerPlugin {
|
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
|
|
100
|
+
// The core stream API has no `res.writeHead`; CORS preflight tagging only
|
|
101
|
+
// applies to the compatibility response that exposes it. Runs once per context:
|
|
102
|
+
// the mixed path calls it again from `adopt` once the real response is in place.
|
|
103
|
+
/**
|
|
104
|
+
* @param {{ res: { writeHead?: Function }, instrumented?: boolean }} context
|
|
105
|
+
*/
|
|
106
|
+
function instrumentWriteHead (context) {
|
|
107
|
+
if (!context.instrumented && typeof context.res.writeHead === 'function') {
|
|
108
|
+
context.res.writeHead = web.wrapWriteHead(context)
|
|
109
|
+
context.instrumented = true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
module.exports = Http2ServerPlugin
|
|
@@ -122,28 +122,43 @@ function isMarkedAsUnskippable (test) {
|
|
|
122
122
|
return false
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
|
|
125
|
+
function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRootDir) {
|
|
126
126
|
const unskippableSuites = {}
|
|
127
127
|
const forcedToRunSuites = {}
|
|
128
128
|
|
|
129
129
|
const skippedSuites = []
|
|
130
130
|
const suitesToRun = []
|
|
131
|
+
const normalizedSkippableSuites = new Set(skippableSuites.map(suite => suite.replaceAll('\\', '/')))
|
|
131
132
|
|
|
132
133
|
for (const test of originalTests) {
|
|
133
134
|
const relativePath = getTestSuitePath(test.path, rootDir)
|
|
134
|
-
const
|
|
135
|
+
const testRootDir = test?.context?.config?.rootDir || fallbackRootDir
|
|
136
|
+
let fallbackRelativePath
|
|
137
|
+
let skippedSuite = normalizedSkippableSuites.has(relativePath) ? relativePath : undefined
|
|
138
|
+
if (testRootDir && testRootDir !== rootDir) {
|
|
139
|
+
fallbackRelativePath = getTestSuitePath(test.path, testRootDir)
|
|
140
|
+
if (skippedSuite === undefined && normalizedSkippableSuites.has(fallbackRelativePath)) {
|
|
141
|
+
skippedSuite = fallbackRelativePath
|
|
142
|
+
}
|
|
143
|
+
}
|
|
135
144
|
if (isMarkedAsUnskippable(test)) {
|
|
136
145
|
suitesToRun.push(test)
|
|
137
146
|
unskippableSuites[relativePath] = true
|
|
138
|
-
if (
|
|
147
|
+
if (fallbackRelativePath !== undefined) {
|
|
148
|
+
unskippableSuites[fallbackRelativePath] = true
|
|
149
|
+
}
|
|
150
|
+
if (skippedSuite !== undefined) {
|
|
139
151
|
forcedToRunSuites[relativePath] = true
|
|
152
|
+
if (fallbackRelativePath !== undefined) {
|
|
153
|
+
forcedToRunSuites[fallbackRelativePath] = true
|
|
154
|
+
}
|
|
140
155
|
}
|
|
141
156
|
continue
|
|
142
157
|
}
|
|
143
|
-
if (
|
|
144
|
-
skippedSuites.push(relativePath)
|
|
145
|
-
} else {
|
|
158
|
+
if (skippedSuite === undefined) {
|
|
146
159
|
suitesToRun.push(test)
|
|
160
|
+
} else {
|
|
161
|
+
skippedSuites.push(skippedSuite)
|
|
147
162
|
}
|
|
148
163
|
}
|
|
149
164
|
|
|
@@ -264,8 +264,10 @@ class VitestPlugin extends CiPlugin {
|
|
|
264
264
|
span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
|
|
265
265
|
}
|
|
266
266
|
const finish = () => {
|
|
267
|
-
if (duration) {
|
|
268
|
-
span.finish(
|
|
267
|
+
if (Number.isFinite(duration) && duration >= 0) {
|
|
268
|
+
span.finish(
|
|
269
|
+
span._startTime + Math.max(duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION, 0)
|
|
270
|
+
) // milliseconds
|
|
269
271
|
} else {
|
|
270
272
|
span.finish() // `duration` is empty for retries, so we'll use clock time
|
|
271
273
|
}
|
|
@@ -1,39 +1,4 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// Set up beforeExitHandlers before loading the tracer so that modules loaded
|
|
7
|
-
// during require('./src') can register handlers.
|
|
8
|
-
Object.defineProperty(globalThis, ddTraceSymbol, {
|
|
9
|
-
value: {
|
|
10
|
-
beforeExitHandlers: new Set(),
|
|
11
|
-
},
|
|
12
|
-
enumerable: false,
|
|
13
|
-
configurable: true,
|
|
14
|
-
writable: false,
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
process.once('beforeExit', function mainBeforeExit () {
|
|
18
|
-
if (globalThis[ddTraceSymbol]?.beforeExitHandlers) {
|
|
19
|
-
for (const handler of globalThis[ddTraceSymbol].beforeExitHandlers) {
|
|
20
|
-
handler()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const TracerProxy = require('./src')
|
|
26
|
-
|
|
27
|
-
Object.defineProperty(global, '_ddtrace', {
|
|
28
|
-
value: new TracerProxy(),
|
|
29
|
-
enumerable: false,
|
|
30
|
-
configurable: true,
|
|
31
|
-
writable: true,
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = global._ddtrace
|
|
36
|
-
// Static aliases so cjs-module-lexer surfaces them as ESM named exports
|
|
37
|
-
// (`import { tracer } from 'dd-trace'`).
|
|
38
|
-
module.exports.tracer = global._ddtrace
|
|
39
|
-
module.exports.default = global._ddtrace
|
|
3
|
+
require('./src/openfeature/register')
|
|
4
|
+
module.exports = require('./src/bootstrap')
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
if (!global._ddtrace) {
|
|
4
|
+
const ddTraceSymbol = Symbol.for('dd-trace')
|
|
5
|
+
|
|
6
|
+
// Set up beforeExitHandlers before loading the tracer so that modules loaded
|
|
7
|
+
// during require('./src') can register handlers.
|
|
8
|
+
Object.defineProperty(globalThis, ddTraceSymbol, {
|
|
9
|
+
value: {
|
|
10
|
+
beforeExitHandlers: new Set(),
|
|
11
|
+
},
|
|
12
|
+
enumerable: false,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
process.once('beforeExit', function mainBeforeExit () {
|
|
18
|
+
if (globalThis[ddTraceSymbol]?.beforeExitHandlers) {
|
|
19
|
+
for (const handler of globalThis[ddTraceSymbol].beforeExitHandlers) {
|
|
20
|
+
handler()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const TracerProxy = require('.')
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(global, '_ddtrace', {
|
|
28
|
+
value: new TracerProxy(),
|
|
29
|
+
enumerable: false,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = global._ddtrace
|
|
36
|
+
// Static aliases so cjs-module-lexer surfaces them as ESM named exports
|
|
37
|
+
// (`import { tracer } from 'dd-trace'`).
|
|
38
|
+
module.exports.tracer = global._ddtrace
|
|
39
|
+
module.exports.default = global._ddtrace
|
|
@@ -293,6 +293,9 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
293
293
|
isCoverageReportUploadEnabled,
|
|
294
294
|
} = remoteConfiguration
|
|
295
295
|
const { testOptimization } = this._config
|
|
296
|
+
const earlyFlakeDetectionRetryCount =
|
|
297
|
+
testOptimization.DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT
|
|
298
|
+
const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
|
|
296
299
|
return {
|
|
297
300
|
isCodeCoverageEnabled,
|
|
298
301
|
isSuitesSkippingEnabled,
|
|
@@ -300,8 +303,16 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
300
303
|
requireGit,
|
|
301
304
|
isEarlyFlakeDetectionEnabled:
|
|
302
305
|
isEarlyFlakeDetectionEnabled && testOptimization.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
|
|
303
|
-
earlyFlakeDetectionNumRetries
|
|
304
|
-
|
|
306
|
+
earlyFlakeDetectionNumRetries:
|
|
307
|
+
hasEarlyFlakeDetectionRetryCount ? earlyFlakeDetectionRetryCount : earlyFlakeDetectionNumRetries,
|
|
308
|
+
earlyFlakeDetectionSlowTestRetries: hasEarlyFlakeDetectionRetryCount
|
|
309
|
+
? {
|
|
310
|
+
'5s': earlyFlakeDetectionRetryCount,
|
|
311
|
+
'10s': earlyFlakeDetectionRetryCount,
|
|
312
|
+
'30s': earlyFlakeDetectionRetryCount,
|
|
313
|
+
'5m': earlyFlakeDetectionRetryCount,
|
|
314
|
+
}
|
|
315
|
+
: earlyFlakeDetectionSlowTestRetries,
|
|
305
316
|
earlyFlakeDetectionFaultyThreshold,
|
|
306
317
|
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
|
|
307
318
|
flakyTestRetriesCount: testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
|
|
@@ -318,6 +329,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
318
329
|
|
|
319
330
|
sendGitMetadata (repositoryUrl) {
|
|
320
331
|
if (!this._config.testOptimization.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED) {
|
|
332
|
+
this._resolveGit()
|
|
321
333
|
return
|
|
322
334
|
}
|
|
323
335
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
@@ -102,14 +102,30 @@ class TestWorkerCiVisibilityExporter {
|
|
|
102
102
|
this._logsWriter.append({ testEnvironmentMetadata, logMessage })
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* @param {() => void} [onDone]
|
|
107
|
+
*/
|
|
106
108
|
flush (onDone) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this._telemetryWriter
|
|
109
|
+
if (!onDone) {
|
|
110
|
+
this._writer.flush()
|
|
111
|
+
this._coverageWriter.flush()
|
|
112
|
+
this._logsWriter.flush()
|
|
113
|
+
if (this._telemetryWriter) {
|
|
114
|
+
this._telemetryWriter.flush()
|
|
115
|
+
}
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let pendingWriters = this._telemetryWriter ? 4 : 3
|
|
120
|
+
const onWriterFlushed = () => {
|
|
121
|
+
pendingWriters--
|
|
122
|
+
if (pendingWriters === 0) onDone()
|
|
112
123
|
}
|
|
124
|
+
|
|
125
|
+
this._writer.flush(onWriterFlushed)
|
|
126
|
+
this._coverageWriter.flush(onWriterFlushed)
|
|
127
|
+
this._logsWriter.flush(onWriterFlushed)
|
|
128
|
+
this._telemetryWriter?.flush(onWriterFlushed)
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
|