dd-trace 5.86.0 → 5.88.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/LICENSE-3rdparty.csv +60 -32
- package/ext/exporters.d.ts +1 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +243 -7
- package/package.json +9 -6
- package/packages/datadog-instrumentations/src/ai.js +54 -90
- package/packages/datadog-instrumentations/src/cucumber.js +14 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +55 -14
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +21 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +138 -12
- package/packages/datadog-instrumentations/src/http/client.js +119 -1
- package/packages/datadog-instrumentations/src/jest.js +179 -15
- package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
- package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
- package/packages/datadog-instrumentations/src/mysql2.js +131 -64
- package/packages/datadog-instrumentations/src/playwright.js +9 -1
- package/packages/datadog-instrumentations/src/stripe.js +92 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -0
- package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
- package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
- package/packages/datadog-plugin-azure-functions/src/index.js +53 -37
- package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
- package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
- package/packages/datadog-plugin-cucumber/src/index.js +9 -6
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +33 -0
- package/packages/datadog-plugin-cypress/src/support.js +48 -8
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
- package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
- package/packages/datadog-plugin-mocha/src/index.js +9 -6
- package/packages/datadog-plugin-playwright/src/index.js +10 -6
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/dd-trace/src/appsec/addresses.js +11 -0
- package/packages/dd-trace/src/appsec/channels.js +5 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +302 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
- package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
- package/packages/dd-trace/src/appsec/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +66 -4
- package/packages/dd-trace/src/azure_metadata.js +0 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +14 -1
- 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-visibility-exporter.js +2 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +148 -195
- package/packages/dd-trace/src/config/helper.js +43 -1
- package/packages/dd-trace/src/config/index.js +42 -14
- package/packages/dd-trace/src/config/supported-configurations.json +4115 -510
- package/packages/dd-trace/src/constants.js +0 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
- package/packages/dd-trace/src/datastreams/pathway.js +22 -3
- package/packages/dd-trace/src/datastreams/processor.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -2
- package/packages/dd-trace/src/debugger/devtools_client/index.js +75 -23
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +23 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +168 -36
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +18 -0
- package/packages/dd-trace/src/encode/agentless-json.js +141 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/agent/writer.js +22 -8
- package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
- package/packages/dd-trace/src/exporters/common/agents.js +1 -1
- package/packages/dd-trace/src/exporters/common/request.js +4 -4
- package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
- package/packages/dd-trace/src/llmobs/sdk.js +34 -5
- package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
- package/packages/dd-trace/src/opentracing/span.js +6 -4
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
- package/packages/dd-trace/src/plugins/database.js +57 -45
- package/packages/dd-trace/src/plugins/outbound.js +27 -2
- package/packages/dd-trace/src/plugins/tracing.js +39 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +7 -0
- package/packages/dd-trace/src/plugins/util/test.js +48 -0
- package/packages/dd-trace/src/plugins/util/web.js +8 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
- package/packages/dd-trace/src/propagation-hash/index.js +145 -0
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/startup-log.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
- package/packages/dd-trace/src/plugins/util/serverless.js +0 -8
- package/packages/dd-trace/src/scope/noop/scope.js +0 -21
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
-
const serverless = require('../../dd-trace/src/plugins/util/serverless')
|
|
5
4
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
6
5
|
|
|
7
6
|
const triggerMap = {
|
|
@@ -26,19 +25,51 @@ class AzureFunctionsPlugin extends TracingPlugin {
|
|
|
26
25
|
bindStart (ctx) {
|
|
27
26
|
const meta = getMetaForTrigger(ctx)
|
|
28
27
|
const triggerType = triggerMap[ctx.methodName]
|
|
28
|
+
const isHttpTrigger = triggerType === 'Http'
|
|
29
29
|
const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
|
|
30
|
-
const childOf = isMessagingService ? null : extractTraceContext(this._tracer, ctx)
|
|
31
|
-
const span = this.startSpan(this.operationName(), {
|
|
32
|
-
childOf,
|
|
33
|
-
service: this.serviceName(),
|
|
34
|
-
type: 'serverless',
|
|
35
|
-
meta,
|
|
36
|
-
}, ctx)
|
|
37
|
-
|
|
38
|
-
if (isMessagingService) {
|
|
39
|
-
setSpanLinks(triggerType, this.tracer, span, ctx)
|
|
40
|
-
}
|
|
41
30
|
|
|
31
|
+
let span
|
|
32
|
+
|
|
33
|
+
if (isHttpTrigger) {
|
|
34
|
+
const { httpRequest } = ctx
|
|
35
|
+
const path = (new URL(httpRequest.url)).pathname
|
|
36
|
+
const req = {
|
|
37
|
+
method: httpRequest.method,
|
|
38
|
+
headers: Object.fromEntries(httpRequest.headers),
|
|
39
|
+
url: path,
|
|
40
|
+
}
|
|
41
|
+
// Patch the request to create web context
|
|
42
|
+
const webContext = web.patch(req)
|
|
43
|
+
webContext.config = this.config
|
|
44
|
+
webContext.tracer = this.tracer
|
|
45
|
+
webContext.paths = [path]
|
|
46
|
+
// Creates a standard span and an inferred proxy span if headers are present
|
|
47
|
+
span = web.startServerlessSpanWithInferredProxy(
|
|
48
|
+
this.tracer,
|
|
49
|
+
this.config,
|
|
50
|
+
this.operationName(),
|
|
51
|
+
req,
|
|
52
|
+
ctx
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
span._integrationName = 'azure-functions'
|
|
56
|
+
span.context()._tags.component = 'azure-functions'
|
|
57
|
+
span.addTags(meta)
|
|
58
|
+
webContext.span = span
|
|
59
|
+
webContext.azureFunctionCtx = ctx
|
|
60
|
+
ctx.webContext = webContext
|
|
61
|
+
} else {
|
|
62
|
+
// For non-HTTP triggers, use standard flow
|
|
63
|
+
span = this.startSpan(this.operationName(), {
|
|
64
|
+
service: this.serviceName(),
|
|
65
|
+
type: 'serverless',
|
|
66
|
+
meta,
|
|
67
|
+
}, ctx)
|
|
68
|
+
|
|
69
|
+
if (isMessagingService) {
|
|
70
|
+
setSpanLinks(triggerType, this.tracer, span, ctx)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
42
73
|
ctx.span = span
|
|
43
74
|
return ctx.currentStore
|
|
44
75
|
}
|
|
@@ -48,24 +79,16 @@ class AzureFunctionsPlugin extends TracingPlugin {
|
|
|
48
79
|
ctx.currentStore.span.setTag('error.message', ctx.error)
|
|
49
80
|
}
|
|
50
81
|
|
|
51
|
-
|
|
52
|
-
const {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
asyncStart (ctx) {
|
|
83
|
+
const { methodName, result = {}, webContext } = ctx
|
|
84
|
+
const triggerType = triggerMap[methodName]
|
|
85
|
+
|
|
86
|
+
// For HTTP triggers, use web utilities to finish all spans (including inferred proxy)
|
|
87
|
+
if (triggerType === 'Http') {
|
|
88
|
+
if (webContext) {
|
|
89
|
+
webContext.res = { statusCode: result.status }
|
|
90
|
+
web.finishAll(webContext, 'serverless')
|
|
60
91
|
}
|
|
61
|
-
const context = web.patch(req)
|
|
62
|
-
context.config = this.config
|
|
63
|
-
context.paths = [path]
|
|
64
|
-
context.res = { statusCode: result.status }
|
|
65
|
-
context.span = ctx.currentStore.span
|
|
66
|
-
|
|
67
|
-
serverless.finishSpan(context)
|
|
68
|
-
// Fallback for other trigger types
|
|
69
92
|
} else {
|
|
70
93
|
super.finish()
|
|
71
94
|
}
|
|
@@ -80,6 +103,7 @@ function getMetaForTrigger ({ functionName, methodName, invocationContext }) {
|
|
|
80
103
|
let meta = {
|
|
81
104
|
'aas.function.name': functionName,
|
|
82
105
|
'aas.function.trigger': mapTriggerTag(methodName),
|
|
106
|
+
'span.type': 'serverless',
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
if (triggerMap[methodName] === 'ServiceBus') {
|
|
@@ -112,14 +136,6 @@ function mapTriggerTag (methodName) {
|
|
|
112
136
|
return triggerMap[methodName] || 'Unknown'
|
|
113
137
|
}
|
|
114
138
|
|
|
115
|
-
function extractTraceContext (tracer, ctx) {
|
|
116
|
-
if (triggerMap[ctx.methodName] === 'Http') {
|
|
117
|
-
return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
|
|
118
|
-
}
|
|
119
|
-
// Returning null indicates that the span is a root span
|
|
120
|
-
return null
|
|
121
|
-
}
|
|
122
|
-
|
|
123
139
|
// message & messages & batch with cardinality of 1 == applicationProperties
|
|
124
140
|
// messages with cardinality of many == applicationPropertiesArray
|
|
125
141
|
function setSpanLinks (triggerType, tracer, span, ctx) {
|
|
@@ -11,17 +11,24 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
|
|
|
11
11
|
ctx.currentStore?.span?.finish()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
start (ctx) {
|
|
15
|
+
if (!this.config.dsmEnabled) return
|
|
16
|
+
const { span } = ctx.currentStore
|
|
17
|
+
this.setConsumerCheckpoint(span, ctx)
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
bindStart (ctx) {
|
|
15
21
|
const job = ctx.arguments?.[0]
|
|
16
22
|
const queueName = job?.queueName || job?.queue?.name || 'bullmq'
|
|
17
23
|
|
|
18
24
|
let childOf
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
25
|
+
const ddCarrier = this._extractDatadog(job)
|
|
26
|
+
if (ddCarrier) {
|
|
27
|
+
ctx._ddCarrier = ddCarrier
|
|
28
|
+
childOf = this.tracer.extract('text_map', ddCarrier)
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
this.startSpan({
|
|
25
32
|
childOf,
|
|
26
33
|
resource: queueName,
|
|
27
34
|
meta: {
|
|
@@ -33,10 +40,6 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
|
|
|
33
40
|
},
|
|
34
41
|
}, ctx)
|
|
35
42
|
|
|
36
|
-
if (this.config.dsmEnabled) {
|
|
37
|
-
this.setConsumerCheckpoint(span, ctx)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
43
|
return ctx.currentStore
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -47,14 +50,33 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
|
|
|
47
50
|
const queueName = job.queueName || job.queue?.name || 'bullmq'
|
|
48
51
|
const payloadSize = job.data ? getMessageSize(job.data) : 0
|
|
49
52
|
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
this.tracer.decodeDataStreamsContext(
|
|
53
|
+
const ddCarrier = ctx._ddCarrier
|
|
54
|
+
if (ddCarrier) {
|
|
55
|
+
this.tracer.decodeDataStreamsContext(ddCarrier)
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
const edgeTags = ['direction:in', `topic:${queueName}`, 'type:bullmq']
|
|
56
59
|
this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
57
60
|
}
|
|
61
|
+
|
|
62
|
+
_extractDatadog (job) {
|
|
63
|
+
const metadataStr = job?.opts?.telemetry?.metadata
|
|
64
|
+
if (!metadataStr) return
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const metadata = JSON.parse(metadataStr)
|
|
68
|
+
const ddCarrier = metadata._datadog
|
|
69
|
+
if (!ddCarrier) return
|
|
70
|
+
|
|
71
|
+
// Clean up only our _datadog key, preserve other metadata
|
|
72
|
+
delete metadata._datadog
|
|
73
|
+
job.opts.telemetry.metadata = JSON.stringify(metadata)
|
|
74
|
+
|
|
75
|
+
return ddCarrier
|
|
76
|
+
} catch {
|
|
77
|
+
// Ignore malformed metadata
|
|
78
|
+
}
|
|
79
|
+
}
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
module.exports = BullmqConsumerPlugin
|
|
@@ -10,6 +10,12 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
10
10
|
ctx.currentStore?.span?.finish()
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
start (ctx) {
|
|
14
|
+
if (!this.config.dsmEnabled) return
|
|
15
|
+
const { span } = ctx.currentStore
|
|
16
|
+
this.setProducerCheckpoint(span, ctx)
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
bindStart (ctx) {
|
|
14
20
|
const { resource, meta } = this.getSpanData(ctx)
|
|
15
21
|
const span = this.startSpan({
|
|
@@ -25,10 +31,6 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
25
31
|
|
|
26
32
|
this.injectTraceContext(span, ctx)
|
|
27
33
|
|
|
28
|
-
if (this.config.dsmEnabled) {
|
|
29
|
-
this.setProducerCheckpoint(span, ctx)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
34
|
return ctx.currentStore
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -40,13 +42,24 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
40
42
|
throw new Error('injectTraceContext must be implemented by subclass')
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
_injectIntoOpts (span, opts) {
|
|
46
|
+
const carrier = {}
|
|
47
|
+
this.tracer.inject(span, 'text_map', carrier)
|
|
48
|
+
const existing = opts.telemetry?.metadata ? JSON.parse(opts.telemetry.metadata) : {}
|
|
49
|
+
existing._datadog = carrier
|
|
50
|
+
opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
setProducerCheckpoint (span, ctx) {
|
|
44
|
-
const { queueName, payloadSize,
|
|
54
|
+
const { queueName, payloadSize, optsTarget } = this.getDsmData(ctx)
|
|
45
55
|
const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
|
|
46
56
|
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
|
|
58
|
+
if (optsTarget && typeof optsTarget === 'object') {
|
|
59
|
+
const existing = optsTarget.telemetry?.metadata ? JSON.parse(optsTarget.telemetry.metadata) : {}
|
|
60
|
+
DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
|
|
61
|
+
if (!existing._datadog) existing._datadog = {}
|
|
62
|
+
optsTarget.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
|
|
@@ -68,12 +81,22 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
|
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
#ensureOpts (ctx) {
|
|
85
|
+
let opts = ctx.arguments?.[2]
|
|
86
|
+
if (!opts || typeof opts !== 'object') {
|
|
87
|
+
opts = {}
|
|
88
|
+
if (ctx.arguments.length <= 2) {
|
|
89
|
+
Array.prototype.push.call(ctx.arguments, opts)
|
|
90
|
+
} else {
|
|
91
|
+
ctx.arguments[2] = opts
|
|
92
|
+
}
|
|
76
93
|
}
|
|
94
|
+
return opts
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
injectTraceContext (span, ctx) {
|
|
98
|
+
const opts = this.#ensureOpts(ctx)
|
|
99
|
+
this._injectIntoOpts(span, opts)
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
getDsmData (ctx) {
|
|
@@ -81,7 +104,7 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
|
|
|
81
104
|
return {
|
|
82
105
|
queueName: ctx.self?.name || 'bullmq',
|
|
83
106
|
payloadSize: data ? getMessageSize(data) : 0,
|
|
84
|
-
|
|
107
|
+
optsTarget: this.#ensureOpts(ctx),
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
}
|
|
@@ -108,10 +131,11 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
|
|
|
108
131
|
injectTraceContext (span, ctx) {
|
|
109
132
|
const jobs = ctx.arguments?.[0]
|
|
110
133
|
if (!Array.isArray(jobs)) return
|
|
134
|
+
|
|
111
135
|
for (const job of jobs) {
|
|
112
|
-
if (job
|
|
113
|
-
job.
|
|
114
|
-
this.
|
|
136
|
+
if (!job) continue
|
|
137
|
+
job.opts = job.opts || {}
|
|
138
|
+
this._injectIntoOpts(span, job.opts)
|
|
115
139
|
}
|
|
116
140
|
}
|
|
117
141
|
|
|
@@ -123,7 +147,7 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
|
|
|
123
147
|
return {
|
|
124
148
|
queueName: ctx.self?.name || 'bullmq',
|
|
125
149
|
payloadSize,
|
|
126
|
-
|
|
150
|
+
optsTarget: jobs[0]?.opts,
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
153
|
|
|
@@ -133,12 +157,14 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
|
|
|
133
157
|
const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
|
|
134
158
|
|
|
135
159
|
for (const job of jobs) {
|
|
136
|
-
if (job?.data
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
if (!job?.data) continue
|
|
161
|
+
const payloadSize = getMessageSize(job.data)
|
|
162
|
+
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
163
|
+
job.opts = job.opts || {}
|
|
164
|
+
const existing = job.opts.telemetry?.metadata ? JSON.parse(job.opts.telemetry.metadata) : {}
|
|
165
|
+
DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
|
|
166
|
+
if (!existing._datadog) existing._datadog = {}
|
|
167
|
+
job.opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
|
|
142
168
|
}
|
|
143
169
|
}
|
|
144
170
|
}
|
|
@@ -159,18 +185,21 @@ class FlowProducerAddPlugin extends BaseBullmqProducerPlugin {
|
|
|
159
185
|
|
|
160
186
|
injectTraceContext (span, ctx) {
|
|
161
187
|
const flow = ctx.arguments?.[0]
|
|
162
|
-
if (flow
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
188
|
+
if (!flow) return
|
|
189
|
+
flow.opts = flow.opts || {}
|
|
190
|
+
this._injectIntoOpts(span, flow.opts)
|
|
166
191
|
}
|
|
167
192
|
|
|
168
193
|
getDsmData (ctx) {
|
|
169
194
|
const flow = ctx.arguments?.[0]
|
|
195
|
+
if (!flow) {
|
|
196
|
+
return { queueName: 'bullmq', payloadSize: 0, optsTarget: undefined }
|
|
197
|
+
}
|
|
198
|
+
flow.opts = flow.opts || {}
|
|
170
199
|
return {
|
|
171
|
-
queueName: flow
|
|
172
|
-
payloadSize: flow
|
|
173
|
-
|
|
200
|
+
queueName: flow.queueName || 'bullmq',
|
|
201
|
+
payloadSize: flow.data ? getMessageSize(flow.data) : 0,
|
|
202
|
+
optsTarget: flow.opts,
|
|
174
203
|
}
|
|
175
204
|
}
|
|
176
205
|
}
|
|
@@ -128,12 +128,15 @@ class CucumberPlugin extends CiPlugin {
|
|
|
128
128
|
const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
|
|
129
129
|
const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
130
130
|
|
|
131
|
-
const testSuiteMetadata =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const testSuiteMetadata = {
|
|
132
|
+
...getTestSuiteCommonTags(
|
|
133
|
+
this.command,
|
|
134
|
+
this.frameworkVersion,
|
|
135
|
+
testSuitePath,
|
|
136
|
+
'cucumber'
|
|
137
|
+
),
|
|
138
|
+
...this.getSessionRequestErrorTags(),
|
|
139
|
+
}
|
|
137
140
|
if (isUnskippable) {
|
|
138
141
|
this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
139
142
|
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
@@ -46,6 +46,8 @@ const {
|
|
|
46
46
|
TEST_RETRY_REASON_TYPES,
|
|
47
47
|
getPullRequestDiff,
|
|
48
48
|
getModifiedFilesFromDiff,
|
|
49
|
+
getSessionRequestErrorTags,
|
|
50
|
+
DD_CI_LIBRARY_CONFIGURATION_ERROR,
|
|
49
51
|
TEST_IS_MODIFIED,
|
|
50
52
|
getPullRequestBaseBranch,
|
|
51
53
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
@@ -306,6 +308,9 @@ class CypressPlugin {
|
|
|
306
308
|
|
|
307
309
|
this.isTestIsolationEnabled = getIsTestIsolationEnabled(cypressConfig)
|
|
308
310
|
|
|
311
|
+
const envFlushWait = Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS'))
|
|
312
|
+
this.rumFlushWaitMillis = Number.isFinite(envFlushWait) ? envFlushWait : undefined
|
|
313
|
+
|
|
309
314
|
if (!this.isTestIsolationEnabled) {
|
|
310
315
|
log.warn('Test isolation is disabled, retries will not be enabled')
|
|
311
316
|
}
|
|
@@ -314,10 +319,15 @@ class CypressPlugin {
|
|
|
314
319
|
this.testEnvironmentMetadata[DD_TEST_IS_USER_PROVIDED_SERVICE] =
|
|
315
320
|
tracer._tracer._config.isServiceUserProvided ? 'true' : 'false'
|
|
316
321
|
|
|
322
|
+
this._pendingRequestErrorTags = []
|
|
317
323
|
this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
|
|
318
324
|
.then((libraryConfigurationResponse) => {
|
|
319
325
|
if (libraryConfigurationResponse.err) {
|
|
320
326
|
log.error('Cypress plugin library config response error', libraryConfigurationResponse.err)
|
|
327
|
+
this._pendingRequestErrorTags.push({
|
|
328
|
+
tag: DD_CI_LIBRARY_CONFIGURATION_ERROR,
|
|
329
|
+
value: 'true',
|
|
330
|
+
})
|
|
321
331
|
} else {
|
|
322
332
|
const {
|
|
323
333
|
libraryConfig: {
|
|
@@ -412,6 +422,7 @@ class CypressPlugin {
|
|
|
412
422
|
if (this.testSessionSpan && this.testModuleSpan) {
|
|
413
423
|
testSuiteTags[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
|
|
414
424
|
testSuiteTags[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
|
|
425
|
+
Object.assign(testSuiteTags, this.getSessionRequestErrorTags())
|
|
415
426
|
// If testSuiteSpan couldn't be created, we'll use the testModuleSpan as the parent
|
|
416
427
|
if (!this.testSuiteSpan) {
|
|
417
428
|
testSuiteTags[TEST_SUITE_ID] = this.testModuleSpan.context().toSpanId()
|
|
@@ -468,6 +479,14 @@ class CypressPlugin {
|
|
|
468
479
|
})
|
|
469
480
|
}
|
|
470
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Returns request error tags from the test session span for propagation to test spans.
|
|
484
|
+
* @returns {Record<string, string>}
|
|
485
|
+
*/
|
|
486
|
+
getSessionRequestErrorTags () {
|
|
487
|
+
return getSessionRequestErrorTags(this.testSessionSpan)
|
|
488
|
+
}
|
|
489
|
+
|
|
471
490
|
ciVisEvent (name, testLevel, tags = {}) {
|
|
472
491
|
incrementCountMetric(name, {
|
|
473
492
|
testLevel,
|
|
@@ -596,14 +615,20 @@ class CypressPlugin {
|
|
|
596
615
|
},
|
|
597
616
|
integrationName: TEST_FRAMEWORK_NAME,
|
|
598
617
|
})
|
|
618
|
+
for (const { tag, value } of this._pendingRequestErrorTags) {
|
|
619
|
+
this.testSessionSpan.setTag(tag, value)
|
|
620
|
+
}
|
|
621
|
+
this._pendingRequestErrorTags = []
|
|
599
622
|
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
|
|
600
623
|
|
|
624
|
+
const sessionRequestErrorTags = getSessionRequestErrorTags(this.testSessionSpan)
|
|
601
625
|
this.testModuleSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
|
|
602
626
|
childOf: this.testSessionSpan,
|
|
603
627
|
tags: {
|
|
604
628
|
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
605
629
|
...this.testEnvironmentMetadata,
|
|
606
630
|
...testModuleSpanMetadata,
|
|
631
|
+
...sessionRequestErrorTags,
|
|
607
632
|
},
|
|
608
633
|
integrationName: TEST_FRAMEWORK_NAME,
|
|
609
634
|
})
|
|
@@ -823,6 +848,7 @@ class CypressPlugin {
|
|
|
823
848
|
isModifiedTest: this.getIsTestModified(testSuiteAbsolutePath),
|
|
824
849
|
repositoryRoot: this.repositoryRoot,
|
|
825
850
|
isTestIsolationEnabled: this.isTestIsolationEnabled,
|
|
851
|
+
rumFlushWaitMillis: this.rumFlushWaitMillis,
|
|
826
852
|
}
|
|
827
853
|
|
|
828
854
|
if (this.testSuiteSpan) {
|
|
@@ -937,6 +963,13 @@ class CypressPlugin {
|
|
|
937
963
|
this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
938
964
|
}
|
|
939
965
|
}
|
|
966
|
+
// Check if all EFD retries failed
|
|
967
|
+
if ((isNew || isModified) && this.earlyFlakeDetectionNumRetries > 0) {
|
|
968
|
+
const isLastEfdAttempt = testStatuses.length === this.earlyFlakeDetectionNumRetries + 1
|
|
969
|
+
if (isLastEfdAttempt && testStatuses.every(status => status === 'fail')) {
|
|
970
|
+
this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
|
|
971
|
+
}
|
|
972
|
+
}
|
|
940
973
|
if (isAttemptToFix) {
|
|
941
974
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
|
|
942
975
|
if (testStatuses.length > 1) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
|
|
4
|
+
let rumFlushWaitMillis = 500
|
|
5
|
+
|
|
3
6
|
let isEarlyFlakeDetectionEnabled = false
|
|
4
7
|
let isKnownTestsEnabled = false
|
|
5
8
|
let knownTestsForSuite = []
|
|
@@ -15,9 +18,10 @@ const retryReasonsByTestName = new Map()
|
|
|
15
18
|
// Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
|
|
16
19
|
const quarantinedTestErrors = new Map()
|
|
17
20
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
+
// Track the most recently loaded window in the AUT. Updated via the 'window:load'
|
|
22
|
+
// event so we always get the real app window (after cy.visit()), not the
|
|
23
|
+
// about:blank window that exists when beforeEach runs. If the test later navigates
|
|
24
|
+
// to a cross-origin URL, safeGetRum() handles the access error.
|
|
21
25
|
let originalWindow
|
|
22
26
|
|
|
23
27
|
// If the test is using multi domain with cy.origin, trying to access
|
|
@@ -165,18 +169,45 @@ beforeEach(function () {
|
|
|
165
169
|
retryReasonsByTestName.delete(testName)
|
|
166
170
|
}
|
|
167
171
|
|
|
172
|
+
cy.on('window:load', (win) => {
|
|
173
|
+
originalWindow = win
|
|
174
|
+
})
|
|
175
|
+
|
|
168
176
|
cy.task('dd:beforeEach', {
|
|
169
177
|
testName,
|
|
170
178
|
testSuite: Cypress.mocha.getRootSuite().file,
|
|
171
179
|
}).then(({ traceId, shouldSkip }) => {
|
|
172
|
-
|
|
180
|
+
if (traceId) {
|
|
181
|
+
cy.setCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME, traceId).then(() => {
|
|
182
|
+
// When testIsolation:false, the page is not reset between tests, so the RUM session
|
|
183
|
+
// stopped in afterEach must be explicitly restarted so events in this test are
|
|
184
|
+
// associated with the new testExecutionId.
|
|
185
|
+
//
|
|
186
|
+
// After stopSession(), the RUM SDK creates a new session upon a user interaction
|
|
187
|
+
// (click, scroll, keydown, or touchstart). We dispatch a synthetic click on the window
|
|
188
|
+
// to trigger session renewal, then call startView() to establish a view boundary.
|
|
189
|
+
if (!isTestIsolationEnabled && originalWindow) {
|
|
190
|
+
const rum = safeGetRum(originalWindow)
|
|
191
|
+
if (rum) {
|
|
192
|
+
try {
|
|
193
|
+
const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
|
|
194
|
+
// The browser-sdk addEventListener wrapper filters out untrusted synthetic events
|
|
195
|
+
// unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
|
|
196
|
+
// See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
|
|
197
|
+
Object.defineProperty(evt, '__ddIsTrusted', { value: true })
|
|
198
|
+
originalWindow.dispatchEvent(evt)
|
|
199
|
+
} catch {}
|
|
200
|
+
if (rum.startView) {
|
|
201
|
+
rum.startView()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
173
207
|
if (shouldSkip) {
|
|
174
208
|
this.skip()
|
|
175
209
|
}
|
|
176
210
|
})
|
|
177
|
-
cy.window().then(win => {
|
|
178
|
-
originalWindow = win
|
|
179
|
-
})
|
|
180
211
|
})
|
|
181
212
|
|
|
182
213
|
before(function () {
|
|
@@ -195,6 +226,9 @@ before(function () {
|
|
|
195
226
|
isImpactedTestsEnabled = suiteConfig.isImpactedTestsEnabled
|
|
196
227
|
isModifiedTest = suiteConfig.isModifiedTest
|
|
197
228
|
isTestIsolationEnabled = suiteConfig.isTestIsolationEnabled
|
|
229
|
+
if (Number.isFinite(suiteConfig.rumFlushWaitMillis)) {
|
|
230
|
+
rumFlushWaitMillis = suiteConfig.rumFlushWaitMillis
|
|
231
|
+
}
|
|
198
232
|
}
|
|
199
233
|
})
|
|
200
234
|
})
|
|
@@ -241,8 +275,14 @@ afterEach(function () {
|
|
|
241
275
|
testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
|
|
242
276
|
} catch {}
|
|
243
277
|
|
|
244
|
-
|
|
278
|
+
const rum = safeGetRum(originalWindow)
|
|
279
|
+
if (rum) {
|
|
245
280
|
testInfo.isRUMActive = true
|
|
281
|
+
if (rum.stopSession) {
|
|
282
|
+
rum.stopSession()
|
|
283
|
+
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
284
|
+
cy.wait(rumFlushWaitMillis)
|
|
285
|
+
}
|
|
246
286
|
}
|
|
247
287
|
let coverage
|
|
248
288
|
try {
|
|
@@ -182,9 +182,10 @@ class JestPlugin extends CiPlugin {
|
|
|
182
182
|
config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
|
|
183
183
|
config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
|
|
184
184
|
config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
185
|
+
config._ddRequestErrorTags = this.getSessionRequestErrorTags()
|
|
185
186
|
config._ddItrCorrelationId = this.itrCorrelationId
|
|
186
187
|
config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
|
|
187
|
-
config.
|
|
188
|
+
config._ddEarlyFlakeDetectionSlowTestRetries = this.libraryConfig?.earlyFlakeDetectionSlowTestRetries ?? {}
|
|
188
189
|
config._ddRepositoryRoot = this.repositoryRoot
|
|
189
190
|
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
|
|
190
191
|
config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
|
|
@@ -208,6 +209,7 @@ class JestPlugin extends CiPlugin {
|
|
|
208
209
|
_ddTestSessionId: testSessionId,
|
|
209
210
|
_ddTestCommand: testCommand,
|
|
210
211
|
_ddTestModuleId: testModuleId,
|
|
212
|
+
_ddRequestErrorTags: requestErrorTags,
|
|
211
213
|
_ddItrCorrelationId: itrCorrelationId,
|
|
212
214
|
_ddForcedToRun,
|
|
213
215
|
_ddUnskippable,
|
|
@@ -219,7 +221,11 @@ class JestPlugin extends CiPlugin {
|
|
|
219
221
|
'x-datadog-parent-id': testModuleId,
|
|
220
222
|
})
|
|
221
223
|
|
|
222
|
-
const testSuiteMetadata =
|
|
224
|
+
const testSuiteMetadata = {
|
|
225
|
+
...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest'),
|
|
226
|
+
// requestErrorTags from test env options may be undefined
|
|
227
|
+
...(requestErrorTags !== undefined && requestErrorTags !== null ? requestErrorTags : {}),
|
|
228
|
+
}
|
|
223
229
|
|
|
224
230
|
if (_ddUnskippable) {
|
|
225
231
|
const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
|
|
@@ -389,6 +395,7 @@ class JestPlugin extends CiPlugin {
|
|
|
389
395
|
attemptToFixFailed,
|
|
390
396
|
isAtrRetry,
|
|
391
397
|
finalStatus,
|
|
398
|
+
earlyFlakeAbortReason,
|
|
392
399
|
}) => {
|
|
393
400
|
span.setTag(TEST_STATUS, status)
|
|
394
401
|
if (finalStatus) {
|
|
@@ -409,6 +416,9 @@ class JestPlugin extends CiPlugin {
|
|
|
409
416
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
410
417
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
411
418
|
}
|
|
419
|
+
if (earlyFlakeAbortReason) {
|
|
420
|
+
span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
|
|
421
|
+
}
|
|
412
422
|
|
|
413
423
|
this.telemetry.ciVisEvent(
|
|
414
424
|
TELEMETRY_EVENT_FINISHED,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { readFileSync } = require('fs')
|
|
4
4
|
const { parse } = require('../../../vendor/dist/jest-docblock')
|
|
5
5
|
|
|
6
|
-
const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
|
|
6
|
+
const { getTestSuitePath, getEfdRetryCount } = require('../../dd-trace/src/plugins/util/test')
|
|
7
7
|
const log = require('../../dd-trace/src/log')
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -172,4 +172,5 @@ module.exports = {
|
|
|
172
172
|
getJestTestName,
|
|
173
173
|
getJestSuitesToRun,
|
|
174
174
|
isMarkedAsUnskippable,
|
|
175
|
+
getEfdRetryCount,
|
|
175
176
|
}
|