dd-trace 5.115.0 → 5.116.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/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +10 -7
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +20 -7
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +26 -0
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- 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
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -197,7 +197,6 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
197
197
|
config: this.config,
|
|
198
198
|
fields: new Map(),
|
|
199
199
|
pathCache: new Map(),
|
|
200
|
-
collapsedPathCache: this.config.collapse ? { byPath: new Map(), byParent: new Map() } : undefined,
|
|
201
200
|
abortController,
|
|
202
201
|
executeSpan: span,
|
|
203
202
|
plugin: this,
|
|
@@ -228,9 +227,13 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
228
227
|
if (!span) return
|
|
229
228
|
|
|
230
229
|
// Synchronous execute() throw (e.g. execute(null, doc)) — error handler
|
|
231
|
-
// already tagged the span
|
|
230
|
+
// already tagged the span.
|
|
232
231
|
if (ctx.error) {
|
|
233
|
-
|
|
232
|
+
if (ctx.ddAborted) {
|
|
233
|
+
span.finish()
|
|
234
|
+
} else {
|
|
235
|
+
this.#finishSpan(ctx, span)
|
|
236
|
+
}
|
|
234
237
|
return ctx.parentStore
|
|
235
238
|
}
|
|
236
239
|
|
|
@@ -239,10 +242,7 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
239
242
|
if (typeof result?.then === 'function') {
|
|
240
243
|
result.then(
|
|
241
244
|
(res) => this.#finishSpan(ctx, span, res),
|
|
242
|
-
(err) =>
|
|
243
|
-
span.setTag('error', err)
|
|
244
|
-
this.#drain(ctx, span)
|
|
245
|
-
}
|
|
245
|
+
(err) => this.#finishSpan(ctx, span, undefined, err)
|
|
246
246
|
)
|
|
247
247
|
} else {
|
|
248
248
|
this.#finishSpan(ctx, span, result)
|
|
@@ -261,8 +261,16 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
/**
|
|
265
|
+
* @param {object} ctx
|
|
266
|
+
* @param {import('../../dd-trace/src/opentracing/span')} span
|
|
267
|
+
* @param {import('graphql').ExecutionResult} [res]
|
|
268
|
+
* @param {unknown} [error]
|
|
269
|
+
*/
|
|
270
|
+
#finishSpan (ctx, span, res, error) {
|
|
271
|
+
if (error !== undefined) {
|
|
272
|
+
span.setTag('error', error)
|
|
273
|
+
}
|
|
266
274
|
|
|
267
275
|
if (res?.errors?.length) {
|
|
268
276
|
span.setTag('error', res.errors[0])
|
|
@@ -271,17 +279,16 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
271
279
|
}
|
|
272
280
|
}
|
|
273
281
|
|
|
274
|
-
this.#drain(ctx, span)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#drain (ctx, span) {
|
|
278
|
-
span.finish()
|
|
279
282
|
if (ctx.ddContextValue) {
|
|
280
283
|
contexts.delete(ctx.ddContextValue)
|
|
281
284
|
}
|
|
282
285
|
if (ctx.ddInstrumentedArgs) {
|
|
283
286
|
instrumentedArgs.delete(ctx.ddInstrumentedArgs)
|
|
284
287
|
}
|
|
288
|
+
|
|
289
|
+
this.config.hooks.execute(span, ctx.ddArgs, res)
|
|
290
|
+
|
|
291
|
+
span.finish()
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
// Public — called from wrapResolve (free function, crosses class boundary).
|
|
@@ -307,6 +314,7 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
307
314
|
type: 'graphql',
|
|
308
315
|
startTime,
|
|
309
316
|
meta: {
|
|
317
|
+
'graphql.field.coordinates': `${field.parentTypeName}.${fieldName}`,
|
|
310
318
|
'graphql.field.name': fieldName,
|
|
311
319
|
'graphql.field.path': collapsedKey,
|
|
312
320
|
'graphql.field.type': baseTypeName,
|
|
@@ -423,20 +431,35 @@ function wrapResolve (resolve) {
|
|
|
423
431
|
return resolve.apply(this, arguments)
|
|
424
432
|
}
|
|
425
433
|
|
|
426
|
-
const fieldKey = config.collapse ?
|
|
434
|
+
const fieldKey = config.collapse ? pathString : infoPath
|
|
435
|
+
const parentTypeName = info.parentType.name
|
|
427
436
|
let field = rootCtx.fields.get(fieldKey)
|
|
437
|
+
const collapsedField = field
|
|
438
|
+
if (config.collapse && field !== undefined && field.parentTypeName !== parentTypeName) {
|
|
439
|
+
const parentTypeFields = field.parentTypeFields
|
|
440
|
+
if (parentTypeFields?.parentTypeName === undefined) {
|
|
441
|
+
field = parentTypeFields?.get(parentTypeName)
|
|
442
|
+
} else if (parentTypeFields.parentTypeName === parentTypeName) {
|
|
443
|
+
field = parentTypeFields
|
|
444
|
+
} else {
|
|
445
|
+
field = undefined
|
|
446
|
+
}
|
|
447
|
+
if (field && infoPath.typename === undefined) {
|
|
448
|
+
cacheFieldByPath(rootCtx, infoPath, field)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
428
451
|
const isFirst = !field
|
|
429
452
|
|
|
430
453
|
if (isFirst) {
|
|
431
454
|
field = {
|
|
432
455
|
fieldNode: info.fieldNodes?.[0],
|
|
433
456
|
fieldName: info.fieldName,
|
|
457
|
+
parentTypeName,
|
|
434
458
|
returnType: info.returnType,
|
|
435
459
|
baseTypeName: getBaseTypeName(info.returnType),
|
|
436
460
|
variableValues: info.variableValues,
|
|
437
461
|
args,
|
|
438
462
|
infoPath,
|
|
439
|
-
fieldKey,
|
|
440
463
|
pathString,
|
|
441
464
|
collapsedKey: collapsedKey ?? pathString,
|
|
442
465
|
span: null,
|
|
@@ -445,7 +468,25 @@ function wrapResolve (resolve) {
|
|
|
445
468
|
parentStore: null,
|
|
446
469
|
currentStore: null,
|
|
447
470
|
}
|
|
448
|
-
|
|
471
|
+
if (config.collapse && collapsedField) {
|
|
472
|
+
const parentTypeFields = collapsedField.parentTypeFields
|
|
473
|
+
if (parentTypeFields === undefined) {
|
|
474
|
+
collapsedField.parentTypeFields = field
|
|
475
|
+
} else if (parentTypeFields.parentTypeName === undefined) {
|
|
476
|
+
parentTypeFields.set(parentTypeName, field)
|
|
477
|
+
} else {
|
|
478
|
+
const fieldsByParentType = new Map()
|
|
479
|
+
.set(collapsedField.parentTypeName, collapsedField)
|
|
480
|
+
.set(parentTypeFields.parentTypeName, parentTypeFields)
|
|
481
|
+
.set(parentTypeName, field)
|
|
482
|
+
collapsedField.parentTypeFields = fieldsByParentType
|
|
483
|
+
}
|
|
484
|
+
if (infoPath.typename === undefined) {
|
|
485
|
+
cacheFieldByPath(rootCtx, infoPath, field)
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
rootCtx.fields.set(fieldKey, field)
|
|
489
|
+
}
|
|
449
490
|
}
|
|
450
491
|
|
|
451
492
|
// Collapsed siblings still publish updateField (master's contract: one
|
|
@@ -592,31 +633,18 @@ function buildCachedPathString (path, cache, collapse) {
|
|
|
592
633
|
return pathString
|
|
593
634
|
}
|
|
594
635
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (siblings === undefined) {
|
|
608
|
-
siblings = new Map()
|
|
609
|
-
cache.byParent.set(prev, siblings)
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
let collapsedPath = siblings.get(segment)
|
|
613
|
-
if (collapsedPath === undefined) {
|
|
614
|
-
collapsedPath = { key: segment, prev }
|
|
615
|
-
siblings.set(segment, collapsedPath)
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
cache.byPath.set(path, collapsedPath)
|
|
619
|
-
return collapsedPath
|
|
636
|
+
/**
|
|
637
|
+
* @param {{ hasFieldsByPath?: boolean, fields: Map<string|object, object> }} rootCtx
|
|
638
|
+
* @param {object} path
|
|
639
|
+
* @param {object} field
|
|
640
|
+
*/
|
|
641
|
+
function cacheFieldByPath (rootCtx, path, field) {
|
|
642
|
+
// Leaf fields cannot parent resolver spans, so their concrete paths are never read.
|
|
643
|
+
if (field.fieldNode?.selectionSet === undefined) return
|
|
644
|
+
|
|
645
|
+
// Concrete info path objects cannot collide with collapsed path string keys.
|
|
646
|
+
rootCtx.hasFieldsByPath = true
|
|
647
|
+
rootCtx.fields.set(path, field)
|
|
620
648
|
}
|
|
621
649
|
|
|
622
650
|
// Depth filtering directly on the linked-list node — no array allocation needed.
|
|
@@ -640,9 +668,23 @@ function shouldInstrumentNode (config, path) {
|
|
|
640
668
|
}
|
|
641
669
|
|
|
642
670
|
function getParentField (rootCtx, field) {
|
|
643
|
-
for (let curr = field.
|
|
644
|
-
const
|
|
645
|
-
|
|
671
|
+
for (let curr = field.infoPath?.prev; curr; curr = curr.prev) {
|
|
672
|
+
const fieldKey = rootCtx.config.collapse ? rootCtx.pathCache.get(curr) : curr
|
|
673
|
+
const innerField = rootCtx.fields.get(fieldKey)
|
|
674
|
+
if (innerField) {
|
|
675
|
+
if (curr.typename === undefined) {
|
|
676
|
+
if (rootCtx.hasFieldsByPath) {
|
|
677
|
+
const fieldByPath = rootCtx.fields.get(curr)
|
|
678
|
+
if (fieldByPath) return fieldByPath
|
|
679
|
+
}
|
|
680
|
+
return innerField
|
|
681
|
+
}
|
|
682
|
+
if (innerField.parentTypeName === curr.typename) return innerField
|
|
683
|
+
|
|
684
|
+
const parentTypeFields = innerField.parentTypeFields
|
|
685
|
+
if (parentTypeFields.parentTypeName === undefined) return parentTypeFields.get(curr.typename)
|
|
686
|
+
return parentTypeFields
|
|
687
|
+
}
|
|
646
688
|
}
|
|
647
689
|
|
|
648
690
|
return null
|
|
@@ -68,8 +68,6 @@ class GraphQLValidatePlugin extends TracingPlugin {
|
|
|
68
68
|
const errors = ctx.result
|
|
69
69
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
70
70
|
|
|
71
|
-
this.config.hooks.validate(span, document, errors)
|
|
72
|
-
|
|
73
71
|
if (errors?.length) {
|
|
74
72
|
span.setTag('error', errors[0])
|
|
75
73
|
for (const err of errors) {
|
|
@@ -77,6 +75,8 @@ class GraphQLValidatePlugin extends TracingPlugin {
|
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
|
|
78
|
+
this.config.hooks.validate(span, document, errors)
|
|
79
|
+
|
|
80
80
|
span.finish()
|
|
81
81
|
|
|
82
82
|
return ctx.parentStore
|
|
@@ -32,7 +32,7 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
32
32
|
const { authority, options, headers = {} } = message
|
|
33
33
|
const sessionDetails = extractSessionDetails(authority, options)
|
|
34
34
|
const path = headers[HTTP2_HEADER_PATH] || '/'
|
|
35
|
-
const pathname = path.split(/[?#]
|
|
35
|
+
const pathname = path.split(/[?#]/, 1)[0]
|
|
36
36
|
const method = headers[HTTP2_HEADER_METHOD] || HTTP2_METHOD_GET
|
|
37
37
|
const base = `${sessionDetails.protocol}//${sessionDetails.host}:${sessionDetails.port}`
|
|
38
38
|
const uri = `${base}${pathname}`
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { getSegment } = require('../../../dd-trace/src/util')
|
|
3
4
|
const LangChainHandler = require('./default')
|
|
4
5
|
|
|
5
6
|
class LangChainEmbeddingHandler extends LangChainHandler {
|
|
6
7
|
extractProvider (instance) {
|
|
7
|
-
return instance.constructor.name
|
|
8
|
+
return getSegment(instance.constructor.name, 'Embeddings', 0).toLowerCase()
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
extractModel (instance) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { getSegment } = require('../../../dd-trace/src/util')
|
|
3
4
|
const LangChainHandler = require('./default')
|
|
4
5
|
|
|
5
6
|
class LangChainLanguageModelHandler extends LangChainHandler {
|
|
6
7
|
extractProvider (instance) {
|
|
7
|
-
return typeof instance._llmType === 'function' && instance._llmType()
|
|
8
|
+
return typeof instance._llmType === 'function' && getSegment(instance._llmType(), '-', 0)
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
extractModel (instance) {
|
|
@@ -15,10 +15,10 @@ function getTokensFromLlmOutput (result) {
|
|
|
15
15
|
for (const tokenNames of [['input', 'prompt'], ['output', 'completion'], ['total']]) {
|
|
16
16
|
let token = 0
|
|
17
17
|
for (const tokenName of tokenNames) {
|
|
18
|
-
const
|
|
18
|
+
const underscore = `${tokenName}_tokens`
|
|
19
19
|
const camelCase = `${tokenName}Tokens`
|
|
20
20
|
|
|
21
|
-
token = tokenUsage[
|
|
21
|
+
token = tokenUsage[underscore] || tokenUsage[camelCase] || token
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
tokens[tokenNames[0]] = token
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { basename } = require('node:path')
|
|
4
|
+
|
|
3
5
|
const { storage } = require('../../datadog-core')
|
|
4
6
|
const id = require('../../dd-trace/src/id')
|
|
5
7
|
const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
|
|
8
|
+
const getConfig = require('../../dd-trace/src/config')
|
|
9
|
+
const {
|
|
10
|
+
SCREENSHOT_UPLOAD_RESULT_ERROR,
|
|
11
|
+
SCREENSHOT_UPLOAD_RESULT_UPLOADED,
|
|
12
|
+
getScreenshotCapturedAtMs,
|
|
13
|
+
getScreenshotUploadResult,
|
|
14
|
+
getScreenshotUploadTag,
|
|
15
|
+
} = require('../../dd-trace/src/ci-visibility/test-screenshot')
|
|
6
16
|
|
|
7
17
|
const {
|
|
8
18
|
finishAllTraceSpans,
|
|
9
19
|
getTestSuiteCommonTags,
|
|
10
20
|
getTestSuitePath,
|
|
11
21
|
isModifiedTest,
|
|
22
|
+
setRumTestCorrelation,
|
|
12
23
|
TEST_BROWSER_NAME,
|
|
13
|
-
TEST_BROWSER_VERSION,
|
|
14
24
|
TEST_CODE_OWNERS,
|
|
15
25
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
16
26
|
TEST_EARLY_FLAKE_ENABLED,
|
|
@@ -51,6 +61,21 @@ const {
|
|
|
51
61
|
const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
|
|
52
62
|
const log = require('../../dd-trace/src/log')
|
|
53
63
|
|
|
64
|
+
const PLAYWRIGHT_FAILURE_SCREENSHOT_RE = /^test-failed-\d+\.png$/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns whether an attachment is an automatic Playwright failure screenshot.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} attachment - Playwright test attachment
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
function isPlaywrightFailureScreenshot (attachment) {
|
|
73
|
+
return attachment?.name === 'screenshot' &&
|
|
74
|
+
attachment.contentType === 'image/png' &&
|
|
75
|
+
typeof attachment.path === 'string' &&
|
|
76
|
+
PLAYWRIGHT_FAILURE_SCREENSHOT_RE.test(basename(attachment.path))
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
class PlaywrightPlugin extends CiPlugin {
|
|
55
80
|
static id = 'playwright'
|
|
56
81
|
|
|
@@ -60,6 +85,8 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
60
85
|
this._testSuiteSpansByTestSuiteAbsolutePath = new Map()
|
|
61
86
|
this.numFailedTests = 0
|
|
62
87
|
this.numFailedSuites = 0
|
|
88
|
+
this.pendingTestFinishes = 0
|
|
89
|
+
this.finishSession = undefined
|
|
63
90
|
|
|
64
91
|
this.addSub('ci:playwright:test:is-modified', ({
|
|
65
92
|
filePath,
|
|
@@ -71,6 +98,30 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
71
98
|
onDone(isModified)
|
|
72
99
|
})
|
|
73
100
|
|
|
101
|
+
this.addSub('ci:playwright:session:start', ({ isFailureScreenshotEnabled }) => {
|
|
102
|
+
if (!getConfig().testOptimization.DD_TEST_FAILURE_SCREENSHOTS_ENABLED) return
|
|
103
|
+
|
|
104
|
+
if (!isFailureScreenshotEnabled) {
|
|
105
|
+
log.warn(
|
|
106
|
+
'%s %s',
|
|
107
|
+
'DD_TEST_FAILURE_SCREENSHOTS_ENABLED is true, but Playwright screenshot capture is disabled.',
|
|
108
|
+
'Set Playwright use.screenshot to "only-on-failure", "on-first-failure", or "on".'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
this.addSub('ci:playwright:session:configuration', ({ isFailureScreenshotEnabled }) => {
|
|
114
|
+
if (!getConfig().testOptimization.DD_TEST_FAILURE_SCREENSHOTS_ENABLED || !isFailureScreenshotEnabled) return
|
|
115
|
+
|
|
116
|
+
if (!this.tracer._exporter?.canUploadTestScreenshots?.()) {
|
|
117
|
+
log.warn(
|
|
118
|
+
'%s %s',
|
|
119
|
+
'DD_TEST_FAILURE_SCREENSHOTS_ENABLED is true, but Playwright screenshot upload is not supported',
|
|
120
|
+
'by the active Test Optimization transport.'
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
74
125
|
this.addSub('ci:playwright:session:finish', ({
|
|
75
126
|
status,
|
|
76
127
|
isEarlyFlakeDetectionEnabled,
|
|
@@ -78,42 +129,51 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
78
129
|
isTestManagementTestsEnabled,
|
|
79
130
|
onDone,
|
|
80
131
|
}) => {
|
|
81
|
-
|
|
82
|
-
|
|
132
|
+
const finishSession = () => {
|
|
133
|
+
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
134
|
+
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
83
135
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
137
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
138
|
+
}
|
|
139
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
140
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
141
|
+
}
|
|
142
|
+
if (status === 'fail' && this.numFailedSuites > 0) {
|
|
143
|
+
let errorMessage = `Test suites failed: ${this.numFailedSuites}.`
|
|
144
|
+
if (this.numFailedTests > 0) {
|
|
145
|
+
errorMessage += ` Tests failed: ${this.numFailedTests}`
|
|
146
|
+
}
|
|
147
|
+
const error = new Error(errorMessage)
|
|
148
|
+
this.testModuleSpan.setTag('error', error)
|
|
149
|
+
this.testSessionSpan.setTag('error', error)
|
|
94
150
|
}
|
|
95
|
-
const error = new Error(errorMessage)
|
|
96
|
-
this.testModuleSpan.setTag('error', error)
|
|
97
|
-
this.testSessionSpan.setTag('error', error)
|
|
98
|
-
}
|
|
99
151
|
|
|
100
|
-
|
|
101
|
-
|
|
152
|
+
if (isTestManagementTestsEnabled) {
|
|
153
|
+
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.testModuleSpan.finish()
|
|
157
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
158
|
+
this.testSessionSpan.finish()
|
|
159
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
160
|
+
finishAllTraceSpans(this.testSessionSpan)
|
|
161
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
162
|
+
provider: this.ciProviderName,
|
|
163
|
+
autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
|
|
164
|
+
})
|
|
165
|
+
appClosingTelemetry()
|
|
166
|
+
this.tracer._exporter.flush(onDone)
|
|
167
|
+
this.numFailedTests = 0
|
|
168
|
+
this.numFailedSuites = 0
|
|
169
|
+
this.finishSession = undefined
|
|
102
170
|
}
|
|
103
171
|
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
110
|
-
provider: this.ciProviderName,
|
|
111
|
-
autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
|
|
112
|
-
})
|
|
113
|
-
appClosingTelemetry()
|
|
114
|
-
this.tracer._exporter.flush(onDone)
|
|
115
|
-
this.numFailedTests = 0
|
|
116
|
-
this.numFailedSuites = 0
|
|
172
|
+
if (this.pendingTestFinishes > 0) {
|
|
173
|
+
this.finishSession = finishSession
|
|
174
|
+
} else {
|
|
175
|
+
finishSession()
|
|
176
|
+
}
|
|
117
177
|
})
|
|
118
178
|
|
|
119
179
|
this.addBind('ci:playwright:test-suite:start', (ctx) => {
|
|
@@ -178,42 +238,17 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
178
238
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
179
239
|
})
|
|
180
240
|
|
|
181
|
-
this.addSub('ci:playwright:test:page-goto', ({
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}) => {
|
|
185
|
-
const store = storage('legacy').getStore()
|
|
186
|
-
const span = store && store.span
|
|
187
|
-
if (!span) {
|
|
241
|
+
this.addSub('ci:playwright:test:page-goto', (ctx) => {
|
|
242
|
+
const activeSpan = storage('legacy').getStore()?.span
|
|
243
|
+
if (!setRumTestCorrelation(ctx, activeSpan)) {
|
|
188
244
|
log.error('ci:playwright:test:page-goto: test span not found')
|
|
189
|
-
return
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (isRumActive) {
|
|
193
|
-
span.setTag(TEST_IS_RUM_ACTIVE, 'true')
|
|
194
|
-
|
|
195
|
-
if (page) {
|
|
196
|
-
const browserVersion = page.context().browser().version()
|
|
197
|
-
|
|
198
|
-
if (browserVersion) {
|
|
199
|
-
span.setTag(TEST_BROWSER_VERSION, browserVersion)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const url = page.url()
|
|
203
|
-
const domain = new URL(url).hostname
|
|
204
|
-
page.context().addCookies([{
|
|
205
|
-
name: 'datadog-ci-visibility-test-execution-id',
|
|
206
|
-
value: span.context().toTraceId(),
|
|
207
|
-
domain,
|
|
208
|
-
path: '/',
|
|
209
|
-
}])
|
|
210
|
-
}
|
|
211
245
|
}
|
|
212
246
|
})
|
|
213
247
|
|
|
214
|
-
this.addSub('ci:playwright:worker:report', (serializedTraces) => {
|
|
248
|
+
this.addSub('ci:playwright:worker:report', ({ serializedTraces, screenshots }) => {
|
|
215
249
|
const traces = JSON.parse(serializedTraces)
|
|
216
250
|
const formattedTraces = []
|
|
251
|
+
let formattedTestSpan
|
|
217
252
|
|
|
218
253
|
for (const trace of traces) {
|
|
219
254
|
const formattedTrace = []
|
|
@@ -228,6 +263,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
228
263
|
formattedSpan.meta[TEST_HAS_DYNAMIC_NAME] = 'true'
|
|
229
264
|
}
|
|
230
265
|
if (span.name === 'playwright.test') {
|
|
266
|
+
formattedTestSpan = formattedSpan
|
|
231
267
|
// TODO: remove this comment
|
|
232
268
|
// TODO: Let's pass rootDir, repositoryRoot, command, session id and module id as env vars
|
|
233
269
|
// so we don't need this re-serialization logic. This can be passed just once, since they're unique
|
|
@@ -262,9 +298,36 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
262
298
|
formattedTraces.push(formattedTrace)
|
|
263
299
|
}
|
|
264
300
|
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
const exportTraces = () => {
|
|
302
|
+
for (const trace of formattedTraces) {
|
|
303
|
+
this.tracer._exporter.export(trace)
|
|
304
|
+
}
|
|
267
305
|
}
|
|
306
|
+
|
|
307
|
+
if (!formattedTestSpan || !screenshots) {
|
|
308
|
+
exportTraces()
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
this.pendingTestFinishes++
|
|
313
|
+
const uploadStarted = this.uploadTestScreenshots({
|
|
314
|
+
screenshots,
|
|
315
|
+
traceId: formattedTestSpan.trace_id.toString(10),
|
|
316
|
+
}, (screenshotUploadResult) => {
|
|
317
|
+
const screenshotUploadTag = getScreenshotUploadTag(screenshotUploadResult)
|
|
318
|
+
if (screenshotUploadTag) {
|
|
319
|
+
formattedTestSpan.meta[screenshotUploadTag] = 'true'
|
|
320
|
+
}
|
|
321
|
+
exportTraces()
|
|
322
|
+
this.pendingTestFinishes--
|
|
323
|
+
if (this.pendingTestFinishes === 0 && this.finishSession) {
|
|
324
|
+
this.finishSession()
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
if (uploadStarted) return
|
|
328
|
+
|
|
329
|
+
this.pendingTestFinishes--
|
|
330
|
+
exportTraces()
|
|
268
331
|
})
|
|
269
332
|
|
|
270
333
|
this.addBind('ci:playwright:test:start', (ctx) => {
|
|
@@ -480,6 +543,56 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
480
543
|
})
|
|
481
544
|
}
|
|
482
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Uploads automatic failure screenshots for a Playwright test attempt.
|
|
548
|
+
*
|
|
549
|
+
* @param {object} options - Upload options
|
|
550
|
+
* @param {Array<object>} options.screenshots - Playwright test attachments
|
|
551
|
+
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
552
|
+
* @param {(result: string|undefined) => void} onDone - Completion callback
|
|
553
|
+
* @returns {boolean} Whether at least one upload was started
|
|
554
|
+
*/
|
|
555
|
+
uploadTestScreenshots ({ screenshots, traceId }, onDone) {
|
|
556
|
+
const exporter = this.tracer?._exporter
|
|
557
|
+
if (!Array.isArray(screenshots) || !screenshots.length ||
|
|
558
|
+
!exporter?.canUploadTestScreenshots?.() ||
|
|
559
|
+
!exporter.uploadTestScreenshot) {
|
|
560
|
+
return false
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const screenshotPaths = new Set()
|
|
564
|
+
for (const screenshot of screenshots) {
|
|
565
|
+
if (isPlaywrightFailureScreenshot(screenshot)) {
|
|
566
|
+
screenshotPaths.add(screenshot.path)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (!screenshotPaths.size) return false
|
|
570
|
+
|
|
571
|
+
const uploadResults = new Array(screenshotPaths.size)
|
|
572
|
+
let pendingUploads = screenshotPaths.size
|
|
573
|
+
let index = 0
|
|
574
|
+
for (const filePath of screenshotPaths) {
|
|
575
|
+
const resultIndex = index++
|
|
576
|
+
exporter.uploadTestScreenshot({
|
|
577
|
+
filePath,
|
|
578
|
+
traceId,
|
|
579
|
+
idempotencyKey: `${traceId}:${basename(filePath)}`,
|
|
580
|
+
capturedAtMs: getScreenshotCapturedAtMs(filePath, filePath),
|
|
581
|
+
}, (error, uploaded = true) => {
|
|
582
|
+
if (uploaded) {
|
|
583
|
+
uploadResults[resultIndex] = error
|
|
584
|
+
? SCREENSHOT_UPLOAD_RESULT_ERROR
|
|
585
|
+
: SCREENSHOT_UPLOAD_RESULT_UPLOADED
|
|
586
|
+
}
|
|
587
|
+
pendingUploads--
|
|
588
|
+
if (pendingUploads === 0) {
|
|
589
|
+
onDone(getScreenshotUploadResult(uploadResults))
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
}
|
|
593
|
+
return true
|
|
594
|
+
}
|
|
595
|
+
|
|
483
596
|
// TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
|
|
484
597
|
startTestSpan (
|
|
485
598
|
testName,
|
|
@@ -24,7 +24,7 @@ class RouterPlugin extends WebPlugin {
|
|
|
24
24
|
let childOf
|
|
25
25
|
if (context !== undefined) {
|
|
26
26
|
const middleware = context.middleware
|
|
27
|
-
childOf = middleware.length === 0 ? context.span : middleware
|
|
27
|
+
childOf = middleware.length === 0 ? context.span : middleware.at(-1)
|
|
28
28
|
} else if (store) {
|
|
29
29
|
childOf = store.span
|
|
30
30
|
}
|
|
@@ -75,12 +75,21 @@ class RouterPlugin extends WebPlugin {
|
|
|
75
75
|
const context = this.#contexts.get(req)
|
|
76
76
|
if (!context) return
|
|
77
77
|
const middleware = context.middleware
|
|
78
|
-
const span = middleware.length === 0 ? context.span : middleware
|
|
78
|
+
const span = middleware.length === 0 ? context.span : middleware.at(-1)
|
|
79
79
|
if (!span) return
|
|
80
80
|
|
|
81
81
|
span.setTag('error', error)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
+
this.addSub(`apm:${this.constructor.id}:middleware:repeat`, ({ req, name, error }) => {
|
|
85
|
+
// The middleware span already finished on the first `next`, so record the
|
|
86
|
+
// repeat on the still-live request span instead of a finished one.
|
|
87
|
+
web.root(req)?.addEvent('middleware.next_called_again', {
|
|
88
|
+
'middleware.name': name || '<anonymous>',
|
|
89
|
+
with_error: Boolean(error && error !== 'route' && error !== 'router'),
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
84
93
|
this.addSub('apm:http:server:request:finish', ({ req }) => {
|
|
85
94
|
const context = this.#contexts.get(req)
|
|
86
95
|
|