dd-trace 5.22.0 → 5.23.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 +2 -0
- package/index.d.ts +20 -8
- package/package.json +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +290 -53
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/openai.js +4 -2
- package/packages/datadog-instrumentations/src/pg.js +59 -4
- package/packages/datadog-instrumentations/src/vitest.js +184 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +17 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +27 -18
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
- package/packages/dd-trace/src/config.js +61 -10
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +26 -2
- package/packages/dd-trace/src/profiling/config.js +5 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
- package/packages/dd-trace/src/proxy.js +10 -3
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
58
58
|
span._spanContext._trace.record = false
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
if (this.
|
|
61
|
+
if (this.config.propagationFilter(uri)) {
|
|
62
62
|
this.tracer.inject(span, HTTP_HEADERS, options.headers)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -71,18 +71,6 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
71
71
|
return message.currentStore
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
shouldInjectTraceHeaders (options, uri) {
|
|
75
|
-
if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
|
|
76
|
-
return false
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!this.config.propagationFilter(uri)) {
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return true
|
|
84
|
-
}
|
|
85
|
-
|
|
86
74
|
bindAsyncStart ({ parentStore }) {
|
|
87
75
|
return parentStore
|
|
88
76
|
}
|
|
@@ -212,31 +200,6 @@ function getHooks (config) {
|
|
|
212
200
|
return { request }
|
|
213
201
|
}
|
|
214
202
|
|
|
215
|
-
function hasAmazonSignature (options) {
|
|
216
|
-
if (!options) {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (options.headers) {
|
|
221
|
-
const headers = Object.keys(options.headers)
|
|
222
|
-
.reduce((prev, next) => Object.assign(prev, {
|
|
223
|
-
[next.toLowerCase()]: options.headers[next]
|
|
224
|
-
}), {})
|
|
225
|
-
|
|
226
|
-
if (headers['x-amz-signature']) {
|
|
227
|
-
return true
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
|
|
231
|
-
return true
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const search = options.search || options.path
|
|
236
|
-
|
|
237
|
-
return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
|
|
238
|
-
}
|
|
239
|
-
|
|
240
203
|
function extractSessionDetails (options) {
|
|
241
204
|
if (typeof options === 'string') {
|
|
242
205
|
return new URL(options).host
|
|
@@ -248,8 +211,4 @@ function extractSessionDetails (options) {
|
|
|
248
211
|
return { host, port }
|
|
249
212
|
}
|
|
250
213
|
|
|
251
|
-
function startsWith (searchString) {
|
|
252
|
-
return value => String(value).startsWith(searchString)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
214
|
module.exports = HttpClientPlugin
|
|
@@ -62,9 +62,7 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
62
62
|
|
|
63
63
|
addHeaderTags(span, headers, HTTP_REQUEST_HEADERS, this.config)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
this.tracer.inject(span, HTTP_HEADERS, headers)
|
|
67
|
-
}
|
|
65
|
+
this.tracer.inject(span, HTTP_HEADERS, headers)
|
|
68
66
|
|
|
69
67
|
message.parentStore = store
|
|
70
68
|
message.currentStore = { ...store, span }
|
|
@@ -134,29 +132,6 @@ function extractSessionDetails (authority, options) {
|
|
|
134
132
|
return { protocol, port, host }
|
|
135
133
|
}
|
|
136
134
|
|
|
137
|
-
function hasAmazonSignature (headers, path) {
|
|
138
|
-
if (headers) {
|
|
139
|
-
headers = Object.keys(headers)
|
|
140
|
-
.reduce((prev, next) => Object.assign(prev, {
|
|
141
|
-
[next.toLowerCase()]: headers[next]
|
|
142
|
-
}), {})
|
|
143
|
-
|
|
144
|
-
if (headers['x-amz-signature']) {
|
|
145
|
-
return true
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
|
|
149
|
-
return true
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return path && path.toLowerCase().indexOf('x-amz-signature=') !== -1
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function startsWith (searchString) {
|
|
157
|
-
return value => String(value).startsWith(searchString)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
135
|
function getStatusValidator (config) {
|
|
161
136
|
if (typeof config.validateStatus === 'function') {
|
|
162
137
|
return config.validateStatus
|
|
@@ -158,7 +158,13 @@ class JestPlugin extends CiPlugin {
|
|
|
158
158
|
})
|
|
159
159
|
})
|
|
160
160
|
|
|
161
|
-
this.addSub('ci:jest:test-suite:start', ({
|
|
161
|
+
this.addSub('ci:jest:test-suite:start', ({
|
|
162
|
+
testSuite,
|
|
163
|
+
testSourceFile,
|
|
164
|
+
testEnvironmentOptions,
|
|
165
|
+
frameworkVersion,
|
|
166
|
+
displayName
|
|
167
|
+
}) => {
|
|
162
168
|
const {
|
|
163
169
|
_ddTestSessionId: testSessionId,
|
|
164
170
|
_ddTestCommand: testCommand,
|
|
@@ -196,6 +202,16 @@ class JestPlugin extends CiPlugin {
|
|
|
196
202
|
if (displayName) {
|
|
197
203
|
testSuiteMetadata[JEST_DISPLAY_NAME] = displayName
|
|
198
204
|
}
|
|
205
|
+
if (testSourceFile) {
|
|
206
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
207
|
+
// Test suite is the whole test file, so we can use the first line as the start
|
|
208
|
+
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
212
|
+
if (codeOwners) {
|
|
213
|
+
testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
214
|
+
}
|
|
199
215
|
|
|
200
216
|
this.testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
|
|
201
217
|
childOf: testSessionSpanContext,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
2
|
+
const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
|
|
3
|
+
|
|
4
|
+
class KafkajsBatchConsumerPlugin extends ConsumerPlugin {
|
|
5
|
+
static get id () { return 'kafkajs' }
|
|
6
|
+
static get operation () { return 'consume-batch' }
|
|
7
|
+
|
|
8
|
+
start ({ topic, partition, messages, groupId }) {
|
|
9
|
+
if (!this.config.dsmEnabled) return
|
|
10
|
+
for (const message of messages) {
|
|
11
|
+
if (!message || !message.headers) continue
|
|
12
|
+
const payloadSize = getMessageSize(message)
|
|
13
|
+
this.tracer.decodeDataStreamsContext(message.headers)
|
|
14
|
+
this.tracer
|
|
15
|
+
.setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], null, payloadSize)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = KafkajsBatchConsumerPlugin
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const dc = require('dc-polyfill')
|
|
4
4
|
const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
|
|
5
|
-
const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
|
|
6
5
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
7
6
|
|
|
8
7
|
const afterStartCh = dc.channel('dd-trace:kafkajs:consumer:afterStart')
|
|
@@ -78,7 +77,7 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
|
|
|
78
77
|
'kafka.partition': partition
|
|
79
78
|
}
|
|
80
79
|
})
|
|
81
|
-
if (this.config.dsmEnabled && message?.headers
|
|
80
|
+
if (this.config.dsmEnabled && message?.headers) {
|
|
82
81
|
const payloadSize = getMessageSize(message)
|
|
83
82
|
this.tracer.decodeDataStreamsContext(message.headers)
|
|
84
83
|
this.tracer
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const ProducerPlugin = require('./producer')
|
|
4
4
|
const ConsumerPlugin = require('./consumer')
|
|
5
|
+
const BatchConsumerPlugin = require('./batch-consumer')
|
|
5
6
|
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
6
7
|
|
|
7
8
|
class KafkajsPlugin extends CompositePlugin {
|
|
@@ -9,7 +10,8 @@ class KafkajsPlugin extends CompositePlugin {
|
|
|
9
10
|
static get plugins () {
|
|
10
11
|
return {
|
|
11
12
|
producer: ProducerPlugin,
|
|
12
|
-
consumer: ConsumerPlugin
|
|
13
|
+
consumer: ConsumerPlugin,
|
|
14
|
+
batchConsumer: BatchConsumerPlugin
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
}
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
TEST_IS_NEW,
|
|
22
22
|
TEST_IS_RETRY,
|
|
23
23
|
TEST_EARLY_FLAKE_ENABLED,
|
|
24
|
+
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
24
25
|
TEST_SESSION_ID,
|
|
25
26
|
TEST_MODULE_ID,
|
|
26
27
|
TEST_MODULE,
|
|
@@ -124,6 +125,19 @@ class MochaPlugin extends CiPlugin {
|
|
|
124
125
|
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
125
126
|
this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
|
|
126
127
|
}
|
|
128
|
+
if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) {
|
|
129
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
130
|
+
} else {
|
|
131
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = testSuite
|
|
132
|
+
}
|
|
133
|
+
if (testSuiteMetadata[TEST_SOURCE_FILE]) {
|
|
134
|
+
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
138
|
+
if (codeOwners) {
|
|
139
|
+
testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
140
|
+
}
|
|
127
141
|
|
|
128
142
|
const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
|
|
129
143
|
childOf: this.testModuleSpan,
|
|
@@ -267,6 +281,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
267
281
|
hasUnskippableSuites,
|
|
268
282
|
error,
|
|
269
283
|
isEarlyFlakeDetectionEnabled,
|
|
284
|
+
isEarlyFlakeDetectionFaulty,
|
|
270
285
|
isParallel
|
|
271
286
|
}) => {
|
|
272
287
|
if (this.testSessionSpan) {
|
|
@@ -301,6 +316,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
301
316
|
if (isEarlyFlakeDetectionEnabled) {
|
|
302
317
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
303
318
|
}
|
|
319
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
320
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
321
|
+
}
|
|
304
322
|
|
|
305
323
|
this.testModuleSpan.finish()
|
|
306
324
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
@@ -276,25 +276,34 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
276
276
|
const completionTokens = spanTags['openai.response.usage.completion_tokens']
|
|
277
277
|
const completionTokensEstimated = spanTags['openai.response.usage.completion_tokens_estimated']
|
|
278
278
|
|
|
279
|
+
const totalTokens = spanTags['openai.response.usage.total_tokens']
|
|
280
|
+
|
|
279
281
|
if (!error) {
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
if (promptTokens != null) {
|
|
283
|
+
if (promptTokensEstimated) {
|
|
284
|
+
this.metrics.distribution(
|
|
285
|
+
'openai.tokens.prompt', promptTokens, [...tags, 'openai.estimated:true'])
|
|
286
|
+
} else {
|
|
287
|
+
this.metrics.distribution('openai.tokens.prompt', promptTokens, tags)
|
|
288
|
+
}
|
|
285
289
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
|
|
291
|
+
if (completionTokens != null) {
|
|
292
|
+
if (completionTokensEstimated) {
|
|
293
|
+
this.metrics.distribution(
|
|
294
|
+
'openai.tokens.completion', completionTokens, [...tags, 'openai.estimated:true'])
|
|
295
|
+
} else {
|
|
296
|
+
this.metrics.distribution('openai.tokens.completion', completionTokens, tags)
|
|
297
|
+
}
|
|
291
298
|
}
|
|
292
299
|
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
if (totalTokens != null) {
|
|
301
|
+
if (promptTokensEstimated || completionTokensEstimated) {
|
|
302
|
+
this.metrics.distribution(
|
|
303
|
+
'openai.tokens.total', totalTokens, [...tags, 'openai.estimated:true'])
|
|
304
|
+
} else {
|
|
305
|
+
this.metrics.distribution('openai.tokens.total', totalTokens, tags)
|
|
306
|
+
}
|
|
298
307
|
}
|
|
299
308
|
}
|
|
300
309
|
|
|
@@ -777,9 +786,9 @@ function usageExtraction (tags, body, methodName, openaiStore) {
|
|
|
777
786
|
if (completionEstimated) tags['openai.response.usage.completion_tokens_estimated'] = true
|
|
778
787
|
}
|
|
779
788
|
|
|
780
|
-
if (promptTokens) tags['openai.response.usage.prompt_tokens'] = promptTokens
|
|
781
|
-
if (completionTokens) tags['openai.response.usage.completion_tokens'] = completionTokens
|
|
782
|
-
if (totalTokens) tags['openai.response.usage.total_tokens'] = totalTokens
|
|
789
|
+
if (promptTokens != null) tags['openai.response.usage.prompt_tokens'] = promptTokens
|
|
790
|
+
if (completionTokens != null) tags['openai.response.usage.completion_tokens'] = completionTokens
|
|
791
|
+
if (totalTokens != null) tags['openai.response.usage.total_tokens'] = totalTokens
|
|
783
792
|
}
|
|
784
793
|
|
|
785
794
|
function truncateApiKey (apiKey) {
|
|
@@ -69,6 +69,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
69
69
|
this.addSub('ci:playwright:test-suite:start', (testSuiteAbsolutePath) => {
|
|
70
70
|
const store = storage.getStore()
|
|
71
71
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
72
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
72
73
|
|
|
73
74
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
74
75
|
this.command,
|
|
@@ -76,6 +77,14 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
76
77
|
testSuite,
|
|
77
78
|
'playwright'
|
|
78
79
|
)
|
|
80
|
+
if (testSourceFile) {
|
|
81
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
|
|
82
|
+
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
83
|
+
}
|
|
84
|
+
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
85
|
+
if (codeOwners) {
|
|
86
|
+
testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
87
|
+
}
|
|
79
88
|
|
|
80
89
|
const testSuiteSpan = this.tracer.startSpan('playwright.test_suite', {
|
|
81
90
|
childOf: this.testModuleSpan,
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const { getAmqpMessageSize } = require('../../dd-trace/src/datastreams/processor')
|
|
6
|
-
const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
|
|
7
6
|
|
|
8
7
|
class RheaConsumerPlugin extends ConsumerPlugin {
|
|
9
8
|
static get id () { return 'rhea' }
|
|
@@ -34,8 +33,7 @@ class RheaConsumerPlugin extends ConsumerPlugin {
|
|
|
34
33
|
|
|
35
34
|
if (
|
|
36
35
|
this.config.dsmEnabled &&
|
|
37
|
-
msgObj?.message?.delivery_annotations
|
|
38
|
-
DsmPathwayCodec.contextExists(msgObj.message.delivery_annotations)
|
|
36
|
+
msgObj?.message?.delivery_annotations
|
|
39
37
|
) {
|
|
40
38
|
const payloadSize = getAmqpMessageSize(
|
|
41
39
|
{ headers: msgObj.message.delivery_annotations, content: msgObj.message.body }
|
|
@@ -6,10 +6,18 @@ const {
|
|
|
6
6
|
finishAllTraceSpans,
|
|
7
7
|
getTestSuitePath,
|
|
8
8
|
getTestSuiteCommonTags,
|
|
9
|
+
getTestSessionName,
|
|
10
|
+
getIsFaultyEarlyFlakeDetection,
|
|
9
11
|
TEST_SOURCE_FILE,
|
|
10
12
|
TEST_IS_RETRY,
|
|
11
13
|
TEST_CODE_COVERAGE_LINES_PCT,
|
|
12
|
-
TEST_CODE_OWNERS
|
|
14
|
+
TEST_CODE_OWNERS,
|
|
15
|
+
TEST_LEVEL_EVENT_TYPES,
|
|
16
|
+
TEST_SESSION_NAME,
|
|
17
|
+
TEST_SOURCE_START,
|
|
18
|
+
TEST_IS_NEW,
|
|
19
|
+
TEST_EARLY_FLAKE_ENABLED,
|
|
20
|
+
TEST_EARLY_FLAKE_ABORT_REASON
|
|
13
21
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
14
22
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
15
23
|
const {
|
|
@@ -33,7 +41,26 @@ class VitestPlugin extends CiPlugin {
|
|
|
33
41
|
|
|
34
42
|
this.taskToFinishTime = new WeakMap()
|
|
35
43
|
|
|
36
|
-
this.addSub('ci:vitest:test:
|
|
44
|
+
this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
|
|
45
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
46
|
+
const testsForThisTestSuite = knownTests[testSuite] || []
|
|
47
|
+
onDone(!testsForThisTestSuite.includes(testName))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
|
|
51
|
+
knownTests,
|
|
52
|
+
testFilepaths,
|
|
53
|
+
onDone
|
|
54
|
+
}) => {
|
|
55
|
+
const isFaulty = getIsFaultyEarlyFlakeDetection(
|
|
56
|
+
testFilepaths.map(testFilepath => getTestSuitePath(testFilepath, this.repositoryRoot)),
|
|
57
|
+
knownTests,
|
|
58
|
+
this.libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
59
|
+
)
|
|
60
|
+
onDone(isFaulty)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry, isNew }) => {
|
|
37
64
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
38
65
|
const store = storage.getStore()
|
|
39
66
|
|
|
@@ -43,6 +70,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
43
70
|
if (isRetry) {
|
|
44
71
|
extraTags[TEST_IS_RETRY] = 'true'
|
|
45
72
|
}
|
|
73
|
+
if (isNew) {
|
|
74
|
+
extraTags[TEST_IS_NEW] = 'true'
|
|
75
|
+
}
|
|
46
76
|
|
|
47
77
|
const span = this.startTestSpan(
|
|
48
78
|
testName,
|
|
@@ -110,6 +140,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
110
140
|
this.testSuiteSpan,
|
|
111
141
|
{
|
|
112
142
|
[TEST_SOURCE_FILE]: testSuite,
|
|
143
|
+
[TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
|
|
113
144
|
[TEST_STATUS]: 'skip'
|
|
114
145
|
}
|
|
115
146
|
)
|
|
@@ -120,12 +151,25 @@ class VitestPlugin extends CiPlugin {
|
|
|
120
151
|
})
|
|
121
152
|
|
|
122
153
|
this.addSub('ci:vitest:test-suite:start', ({ testSuiteAbsolutePath, frameworkVersion }) => {
|
|
154
|
+
this.command = process.env.DD_CIVISIBILITY_TEST_COMMAND
|
|
123
155
|
this.frameworkVersion = frameworkVersion
|
|
124
156
|
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
125
157
|
'x-datadog-trace-id': process.env.DD_CIVISIBILITY_TEST_SESSION_ID,
|
|
126
158
|
'x-datadog-parent-id': process.env.DD_CIVISIBILITY_TEST_MODULE_ID
|
|
127
159
|
})
|
|
128
160
|
|
|
161
|
+
// test suites run in a different process, so they also need to init the metadata dictionary
|
|
162
|
+
const testSessionName = getTestSessionName(this.config, this.command, this.testEnvironmentMetadata)
|
|
163
|
+
const metadataTags = {}
|
|
164
|
+
for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
|
|
165
|
+
metadataTags[testLevel] = {
|
|
166
|
+
[TEST_SESSION_NAME]: testSessionName
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (this.tracer._exporter.setMetadataTags) {
|
|
170
|
+
this.tracer._exporter.setMetadataTags(metadataTags)
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
130
174
|
const testSuiteMetadata = getTestSuiteCommonTags(
|
|
131
175
|
this.command,
|
|
@@ -133,6 +177,14 @@ class VitestPlugin extends CiPlugin {
|
|
|
133
177
|
testSuite,
|
|
134
178
|
'vitest'
|
|
135
179
|
)
|
|
180
|
+
testSuiteMetadata[TEST_SOURCE_FILE] = testSuite
|
|
181
|
+
testSuiteMetadata[TEST_SOURCE_START] = 1
|
|
182
|
+
|
|
183
|
+
const codeOwners = this.getCodeOwners(testSuiteMetadata)
|
|
184
|
+
if (codeOwners) {
|
|
185
|
+
testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
186
|
+
}
|
|
187
|
+
|
|
136
188
|
const testSuiteSpan = this.tracer.startSpan('vitest.test_suite', {
|
|
137
189
|
childOf: testSessionSpanContext,
|
|
138
190
|
tags: {
|
|
@@ -169,7 +221,14 @@ class VitestPlugin extends CiPlugin {
|
|
|
169
221
|
}
|
|
170
222
|
})
|
|
171
223
|
|
|
172
|
-
this.addSub('ci:vitest:session:finish', ({
|
|
224
|
+
this.addSub('ci:vitest:session:finish', ({
|
|
225
|
+
status,
|
|
226
|
+
error,
|
|
227
|
+
testCodeCoverageLinesTotal,
|
|
228
|
+
isEarlyFlakeDetectionEnabled,
|
|
229
|
+
isEarlyFlakeDetectionFaulty,
|
|
230
|
+
onFinish
|
|
231
|
+
}) => {
|
|
173
232
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
174
233
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
175
234
|
if (error) {
|
|
@@ -180,6 +239,12 @@ class VitestPlugin extends CiPlugin {
|
|
|
180
239
|
this.testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
181
240
|
this.testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
182
241
|
}
|
|
242
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
243
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
244
|
+
}
|
|
245
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
246
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
247
|
+
}
|
|
183
248
|
this.testModuleSpan.finish()
|
|
184
249
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
185
250
|
this.testSessionSpan.finish()
|
|
@@ -21,6 +21,8 @@ module.exports = {
|
|
|
21
21
|
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
|
|
22
22
|
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
|
|
23
23
|
responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
|
|
24
|
-
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start')
|
|
25
|
-
|
|
24
|
+
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
|
|
25
|
+
pgQueryStart: dc.channel('apm:pg:query:start'),
|
|
26
|
+
pgPoolQueryStart: dc.channel('datadog:pg:pool:query:start'),
|
|
27
|
+
wafRunFinished: dc.channel('datadog:waf:run:finish')
|
|
26
28
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const web = require('../../plugins/util/web')
|
|
4
|
+
const { setUncaughtExceptionCaptureCallbackStart } = require('../channels')
|
|
5
|
+
const { block } = require('../blocking')
|
|
6
|
+
const ssrf = require('./ssrf')
|
|
7
|
+
const sqli = require('./sql_injection')
|
|
8
|
+
|
|
9
|
+
const { DatadogRaspAbortError } = require('./utils')
|
|
10
|
+
|
|
11
|
+
function removeAllListeners (emitter, event) {
|
|
12
|
+
const listeners = emitter.listeners(event)
|
|
13
|
+
emitter.removeAllListeners(event)
|
|
14
|
+
|
|
15
|
+
let cleaned = false
|
|
16
|
+
return function () {
|
|
17
|
+
if (cleaned === true) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
cleaned = true
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < listeners.length; ++i) {
|
|
23
|
+
emitter.on(event, listeners[i])
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findDatadogRaspAbortError (err, deep = 10) {
|
|
29
|
+
if (err instanceof DatadogRaspAbortError) {
|
|
30
|
+
return err
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (err.cause && deep > 0) {
|
|
34
|
+
return findDatadogRaspAbortError(err.cause, deep - 1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleUncaughtExceptionMonitor (err) {
|
|
39
|
+
const abortError = findDatadogRaspAbortError(err)
|
|
40
|
+
if (!abortError) return
|
|
41
|
+
|
|
42
|
+
const { req, res, blockingAction } = abortError
|
|
43
|
+
block(req, res, web.root(req), null, blockingAction)
|
|
44
|
+
|
|
45
|
+
if (!process.hasUncaughtExceptionCaptureCallback()) {
|
|
46
|
+
const cleanUp = removeAllListeners(process, 'uncaughtException')
|
|
47
|
+
const handler = () => {
|
|
48
|
+
process.removeListener('uncaughtException', handler)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
process.removeListener('uncaughtException', handler)
|
|
53
|
+
cleanUp()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
process.on('uncaughtException', handler)
|
|
57
|
+
} else {
|
|
58
|
+
// uncaughtException event is not executed when hasUncaughtExceptionCaptureCallback is true
|
|
59
|
+
let previousCb
|
|
60
|
+
const cb = ({ currentCallback, abortController }) => {
|
|
61
|
+
setUncaughtExceptionCaptureCallbackStart.unsubscribe(cb)
|
|
62
|
+
if (!currentCallback) {
|
|
63
|
+
abortController.abort()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
previousCb = currentCallback
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setUncaughtExceptionCaptureCallbackStart.subscribe(cb)
|
|
71
|
+
|
|
72
|
+
process.setUncaughtExceptionCaptureCallback(null)
|
|
73
|
+
|
|
74
|
+
// For some reason, previous callback was defined before the instrumentation
|
|
75
|
+
// We can not restore it, so we let the app decide
|
|
76
|
+
if (previousCb) {
|
|
77
|
+
process.setUncaughtExceptionCaptureCallback(() => {
|
|
78
|
+
process.setUncaughtExceptionCaptureCallback(null)
|
|
79
|
+
process.setUncaughtExceptionCaptureCallback(previousCb)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function enable (config) {
|
|
86
|
+
ssrf.enable(config)
|
|
87
|
+
sqli.enable(config)
|
|
88
|
+
|
|
89
|
+
process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function disable () {
|
|
93
|
+
ssrf.disable()
|
|
94
|
+
sqli.disable()
|
|
95
|
+
|
|
96
|
+
process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
enable,
|
|
101
|
+
disable,
|
|
102
|
+
handleUncaughtExceptionMonitor // exported only for testing purpose
|
|
103
|
+
}
|