dd-trace 5.99.0 → 5.100.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 +0 -1
- package/package.json +24 -5
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/jest.js +89 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/utils.js +2 -2
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-memcached/src/index.js +1 -1
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/blocking.js +18 -6
- package/packages/dd-trace/src/appsec/graphql.js +1 -1
- package/packages/dd-trace/src/baggage.js +26 -13
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +45 -69
- package/packages/dd-trace/src/config/index.js +13 -12
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +31 -76
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/dogstatsd.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +3 -3
- package/packages/dd-trace/src/exporter.js +1 -1
- package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
- package/packages/dd-trace/src/llmobs/sdk.js +21 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
- package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -5
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +6 -6
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -42
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +44 -23
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
- package/packages/dd-trace/src/opentracing/span.js +4 -3
- package/packages/dd-trace/src/plugin_manager.js +6 -6
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +295 -29
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +9 -9
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
3
4
|
const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
|
|
4
5
|
const { DsmPathwayCodec, getMessageSize } = require('../../dd-trace/src/datastreams')
|
|
5
6
|
|
|
7
|
+
// Customer-controlled metadata may be malformed JSON. Returning a fresh `{}`
|
|
8
|
+
// on parse failure keeps the publish path alive instead of throwing into
|
|
9
|
+
// `Queue.add` / `Queue.addBulk`.
|
|
10
|
+
function parseTelemetryMetadata (raw) {
|
|
11
|
+
if (!raw) return {}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
log.warn('bullmq: ignoring malformed telemetry.metadata: %s', error.message)
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
7
21
|
static id = 'bullmq'
|
|
8
22
|
|
|
@@ -45,9 +59,9 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
45
59
|
_injectIntoOpts (span, opts) {
|
|
46
60
|
const carrier = {}
|
|
47
61
|
this.tracer.inject(span, 'text_map', carrier)
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
opts.telemetry = { metadata: JSON.stringify(
|
|
62
|
+
const metadata = parseTelemetryMetadata(opts.telemetry?.metadata)
|
|
63
|
+
metadata._datadog = carrier
|
|
64
|
+
opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
setProducerCheckpoint (span, ctx) {
|
|
@@ -56,10 +70,10 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
|
|
|
56
70
|
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
57
71
|
|
|
58
72
|
if (optsTarget && typeof optsTarget === 'object') {
|
|
59
|
-
const
|
|
60
|
-
DsmPathwayCodec.encode(dataStreamsContext,
|
|
61
|
-
if (!
|
|
62
|
-
optsTarget.telemetry = { metadata: JSON.stringify(
|
|
73
|
+
const metadata = parseTelemetryMetadata(optsTarget.telemetry?.metadata)
|
|
74
|
+
DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
|
|
75
|
+
if (!metadata._datadog) metadata._datadog = {}
|
|
76
|
+
optsTarget.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -161,10 +175,10 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
|
|
|
161
175
|
const payloadSize = getMessageSize(job.data)
|
|
162
176
|
const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
|
|
163
177
|
job.opts = job.opts || {}
|
|
164
|
-
const
|
|
165
|
-
DsmPathwayCodec.encode(dataStreamsContext,
|
|
166
|
-
if (!
|
|
167
|
-
job.opts.telemetry = { metadata: JSON.stringify(
|
|
178
|
+
const metadata = parseTelemetryMetadata(job.opts.telemetry?.metadata)
|
|
179
|
+
DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
|
|
180
|
+
if (!metadata._datadog) metadata._datadog = {}
|
|
181
|
+
job.opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
}
|
|
@@ -55,7 +55,9 @@ const {
|
|
|
55
55
|
TEST_IS_MODIFIED,
|
|
56
56
|
TEST_HAS_DYNAMIC_NAME,
|
|
57
57
|
DYNAMIC_NAME_RE,
|
|
58
|
-
|
|
58
|
+
recordAttemptToFixExecution,
|
|
59
|
+
logAttemptToFixTestExecution,
|
|
60
|
+
logTestOptimizationSummary,
|
|
59
61
|
getPullRequestBaseBranch,
|
|
60
62
|
TEST_FINAL_STATUS,
|
|
61
63
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
@@ -278,9 +280,8 @@ function getFinalStatus ({
|
|
|
278
280
|
isQuarantined,
|
|
279
281
|
isDisabled,
|
|
280
282
|
}) {
|
|
281
|
-
// If the test is quarantined or disabled,
|
|
282
|
-
|
|
283
|
-
if (isQuarantined || isDisabled || status === 'skip') {
|
|
283
|
+
// If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
|
|
284
|
+
if (status === 'skip' || (retryKind !== FINAL_STATUS_RETRY_KIND.atf && (isQuarantined || isDisabled))) {
|
|
284
285
|
return 'skip'
|
|
285
286
|
}
|
|
286
287
|
|
|
@@ -322,6 +323,8 @@ class CypressPlugin {
|
|
|
322
323
|
isImpactedTestsEnabled = false
|
|
323
324
|
modifiedFiles = []
|
|
324
325
|
newTestsWithDynamicNames = new Set()
|
|
326
|
+
attemptToFixExecutions = new Map()
|
|
327
|
+
loggedAttemptToFixTests = new Set()
|
|
325
328
|
|
|
326
329
|
constructor () {
|
|
327
330
|
const {
|
|
@@ -394,6 +397,8 @@ class CypressPlugin {
|
|
|
394
397
|
this.testManagementTests = undefined
|
|
395
398
|
this.isImpactedTestsEnabled = false
|
|
396
399
|
this.modifiedFiles = []
|
|
400
|
+
this.attemptToFixExecutions = new Map()
|
|
401
|
+
this.loggedAttemptToFixTests = new Set()
|
|
397
402
|
this.activeTestSpan = null
|
|
398
403
|
this.testSuiteSpan = null
|
|
399
404
|
this.testModuleSpan = null
|
|
@@ -800,7 +805,10 @@ class CypressPlugin {
|
|
|
800
805
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
801
806
|
}
|
|
802
807
|
|
|
803
|
-
|
|
808
|
+
logTestOptimizationSummary({
|
|
809
|
+
attemptToFixExecutions: this.attemptToFixExecutions,
|
|
810
|
+
newTestsWithDynamicNames: this.newTestsWithDynamicNames,
|
|
811
|
+
})
|
|
804
812
|
|
|
805
813
|
this.testModuleSpan.finish()
|
|
806
814
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
@@ -939,10 +947,10 @@ class CypressPlugin {
|
|
|
939
947
|
if (cypressTest.displayError) {
|
|
940
948
|
latestError = new Error(cypressTest.displayError)
|
|
941
949
|
}
|
|
942
|
-
// Update test status - but NOT for quarantined tests where we intentionally
|
|
950
|
+
// Update test status - but NOT for non-ATF quarantined tests where we intentionally
|
|
943
951
|
// report 'fail' to Datadog even though Cypress sees it as 'pass'
|
|
944
952
|
const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
|
|
945
|
-
if (cypressTestStatus !== finishedTest.testStatus && !isQuarantinedTest) {
|
|
953
|
+
if (cypressTestStatus !== finishedTest.testStatus && (!isQuarantinedTest || finishedTest.isAttemptToFix)) {
|
|
946
954
|
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
947
955
|
finishedTest.testSpan.setTag('error', latestError)
|
|
948
956
|
}
|
|
@@ -1050,6 +1058,10 @@ class CypressPlugin {
|
|
|
1050
1058
|
return { shouldSkip: true }
|
|
1051
1059
|
}
|
|
1052
1060
|
|
|
1061
|
+
if (isAttemptToFix) {
|
|
1062
|
+
logAttemptToFixTestExecution(testSuite, testName, this.loggedAttemptToFixTests)
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1053
1065
|
// For disabled tests (not attemptToFix), skip them
|
|
1054
1066
|
if (!isAttemptToFix && isDisabled) {
|
|
1055
1067
|
return { shouldSkip: true }
|
|
@@ -1090,6 +1102,7 @@ class CypressPlugin {
|
|
|
1090
1102
|
isAttemptToFix,
|
|
1091
1103
|
isModified,
|
|
1092
1104
|
isQuarantined: isQuarantinedFromSupport,
|
|
1105
|
+
isDisabled: isDisabledFromSupport,
|
|
1093
1106
|
} = test
|
|
1094
1107
|
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
|
|
1095
1108
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
@@ -1119,6 +1132,7 @@ class CypressPlugin {
|
|
|
1119
1132
|
this.testStatuses[testName] = [testStatus]
|
|
1120
1133
|
}
|
|
1121
1134
|
const testStatuses = this.testStatuses[testName]
|
|
1135
|
+
const activeSpanTags = this.activeTestSpan.context()._tags
|
|
1122
1136
|
|
|
1123
1137
|
if (error) {
|
|
1124
1138
|
this.activeTestSpan.setTag('error', error)
|
|
@@ -1194,6 +1208,13 @@ class CypressPlugin {
|
|
|
1194
1208
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
|
|
1195
1209
|
}
|
|
1196
1210
|
}
|
|
1211
|
+
recordAttemptToFixExecution(this.attemptToFixExecutions, {
|
|
1212
|
+
testSuite,
|
|
1213
|
+
testName,
|
|
1214
|
+
status: testStatus,
|
|
1215
|
+
isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
1216
|
+
isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
|
|
1217
|
+
})
|
|
1197
1218
|
}
|
|
1198
1219
|
// ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
|
|
1199
1220
|
if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
|
|
@@ -1207,6 +1228,9 @@ class CypressPlugin {
|
|
|
1207
1228
|
if (isQuarantinedFromSupport) {
|
|
1208
1229
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
1209
1230
|
}
|
|
1231
|
+
if (isDisabledFromSupport) {
|
|
1232
|
+
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
1233
|
+
}
|
|
1210
1234
|
|
|
1211
1235
|
const finishedTest = {
|
|
1212
1236
|
testName,
|
|
@@ -1222,13 +1246,12 @@ class CypressPlugin {
|
|
|
1222
1246
|
this.finishedTestsByFile[testSuite] = [finishedTest]
|
|
1223
1247
|
}
|
|
1224
1248
|
// test spans are finished at after:spec
|
|
1225
|
-
const activeSpanTags = this.activeTestSpan.context()._tags
|
|
1226
1249
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
1227
1250
|
hasCodeOwners: !!activeSpanTags[TEST_CODE_OWNERS],
|
|
1228
1251
|
isNew,
|
|
1229
1252
|
isRum: isRUMActive,
|
|
1230
1253
|
browserDriver: 'cypress',
|
|
1231
|
-
isQuarantined:
|
|
1254
|
+
isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
|
|
1232
1255
|
isModified,
|
|
1233
1256
|
isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
1234
1257
|
})
|
|
@@ -15,8 +15,8 @@ let isModifiedTest = false
|
|
|
15
15
|
let isTestIsolationEnabled = false
|
|
16
16
|
// Array of test names that have been retried and the reason
|
|
17
17
|
const retryReasonsByTestName = new Map()
|
|
18
|
-
// Track
|
|
19
|
-
const
|
|
18
|
+
// Track test errors suppressed by test management so we can still report them to Datadog.
|
|
19
|
+
const suppressedTestFailures = new Map()
|
|
20
20
|
|
|
21
21
|
// Track the most recently loaded window in the AUT. Updated via the 'window:load'
|
|
22
22
|
// event so we always get the real app window (after cy.visit()), not the
|
|
@@ -61,13 +61,12 @@ Cypress.on('fail', (err, runnable) => {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const testName = runnable.fullTitle()
|
|
64
|
-
const { isQuarantined, isDisabled } = getTestProperties(testName)
|
|
64
|
+
const { isAttemptToFix, isQuarantined, isDisabled } = getTestProperties(testName)
|
|
65
65
|
|
|
66
66
|
// Suppress failures for quarantined or disabled tests so they don't affect the exit code.
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
quarantinedTestErrors.set(testName, err)
|
|
67
|
+
// Attempt-to-fix ignores quarantine/disabled suppression and keeps the normal framework result.
|
|
68
|
+
if (!isAttemptToFix && (isQuarantined || isDisabled)) {
|
|
69
|
+
suppressedTestFailures.set(testName, { error: err, isQuarantined, isDisabled })
|
|
71
70
|
return
|
|
72
71
|
}
|
|
73
72
|
|
|
@@ -245,13 +244,14 @@ afterEach(function () {
|
|
|
245
244
|
const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
|
|
246
245
|
const testName = currentTest.fullTitle()
|
|
247
246
|
|
|
248
|
-
// Check if this was a
|
|
249
|
-
const
|
|
250
|
-
const
|
|
247
|
+
// Check if this was a test management test that we suppressed the failure for.
|
|
248
|
+
const suppressedTestFailure = suppressedTestFailures.get(testName)
|
|
249
|
+
const suppressedError = suppressedTestFailure && suppressedTestFailure.error
|
|
250
|
+
const isTestManagementTestThatFailed = !!suppressedError
|
|
251
251
|
|
|
252
|
-
// For
|
|
253
|
-
const errorToReport =
|
|
254
|
-
? { message:
|
|
252
|
+
// For suppressed test management tests, convert Error to a serializable format for cy.task.
|
|
253
|
+
const errorToReport = isTestManagementTestThatFailed
|
|
254
|
+
? { message: suppressedError.message, stack: suppressedError.stack }
|
|
255
255
|
: currentTest.err
|
|
256
256
|
|
|
257
257
|
const testInfo = {
|
|
@@ -259,16 +259,17 @@ afterEach(function () {
|
|
|
259
259
|
testItTitle: currentTest.title,
|
|
260
260
|
testSuite: Cypress.mocha.getRootSuite().file,
|
|
261
261
|
testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
|
|
262
|
-
//
|
|
263
|
-
state:
|
|
264
|
-
//
|
|
262
|
+
// Report the actual failed state to Datadog, not the pass state Cypress sees after suppression.
|
|
263
|
+
state: isTestManagementTestThatFailed ? 'failed' : currentTest.state,
|
|
264
|
+
// Include the actual error that was suppressed.
|
|
265
265
|
error: errorToReport,
|
|
266
266
|
isNew: currentTest._ddIsNew,
|
|
267
267
|
isEfdRetry: currentTest._ddIsEfdRetry,
|
|
268
268
|
isAttemptToFix: currentTest._ddIsAttemptToFix,
|
|
269
269
|
isModified: currentTest._ddIsModified,
|
|
270
|
-
// Mark
|
|
271
|
-
isQuarantined:
|
|
270
|
+
// Mark suppressed tests so the plugin can tag them with the correct test management reason.
|
|
271
|
+
isQuarantined: isTestManagementTestThatFailed && suppressedTestFailure.isQuarantined,
|
|
272
|
+
isDisabled: isTestManagementTestThatFailed && suppressedTestFailure.isDisabled,
|
|
272
273
|
}
|
|
273
274
|
try {
|
|
274
275
|
const invocationDetails = Cypress.mocha.getRunner().currentRunnable.invocationDetails
|
|
@@ -292,9 +293,9 @@ afterEach(function () {
|
|
|
292
293
|
// ignore error and continue
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
// Clean up the
|
|
296
|
-
if (
|
|
297
|
-
|
|
296
|
+
// Clean up the suppressed error tracking.
|
|
297
|
+
if (isTestManagementTestThatFailed) {
|
|
298
|
+
suppressedTestFailures.delete(testName)
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
cy.task('dd:afterEach', { test: testInfo, coverage })
|
|
@@ -14,7 +14,7 @@ module.exports = class DdTraceApiPlugin extends Plugin {
|
|
|
14
14
|
super(...args)
|
|
15
15
|
|
|
16
16
|
const tracer = this._tracer
|
|
17
|
-
const injectionEnabledTag = `injection_enabled:${this._tracerConfig.
|
|
17
|
+
const injectionEnabledTag = `injection_enabled:${this._tracerConfig.DD_INJECTION_ENABLED ? 'yes' : 'no'}`
|
|
18
18
|
|
|
19
19
|
this.addSub('datadog-api:v1:tracerinit', ({ proxy }) => {
|
|
20
20
|
const proxyVal = proxy()
|
|
@@ -26,8 +26,8 @@ function extractErrorIntoSpanEvent (config, span, exc) {
|
|
|
26
26
|
attributes.message = exc.message
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (config.
|
|
30
|
-
for (const ext of config.
|
|
29
|
+
if (config.DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) {
|
|
30
|
+
for (const ext of config.DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) {
|
|
31
31
|
if (exc.extensions?.[ext]) {
|
|
32
32
|
const value = exc.extensions[ext]
|
|
33
33
|
|
|
@@ -64,7 +64,7 @@ class GrpcClientPlugin extends ClientPlugin {
|
|
|
64
64
|
|
|
65
65
|
error ({ span = this.activeSpan, error }) {
|
|
66
66
|
this.addCode(span, error.code)
|
|
67
|
-
if (error.code && !this._tracerConfig.
|
|
67
|
+
if (error.code && !this._tracerConfig.DD_GRPC_CLIENT_ERROR_STATUSES.includes(error.code)) {
|
|
68
68
|
return
|
|
69
69
|
}
|
|
70
70
|
this.addError(error, span)
|
|
@@ -70,7 +70,7 @@ class GrpcServerPlugin extends ServerPlugin {
|
|
|
70
70
|
if (!span) return
|
|
71
71
|
|
|
72
72
|
this.addCode(span, error.code)
|
|
73
|
-
if (error.code && !this._tracerConfig.
|
|
73
|
+
if (error.code && !this._tracerConfig.DD_GRPC_SERVER_ERROR_STATUSES.includes(error.code)) {
|
|
74
74
|
return
|
|
75
75
|
}
|
|
76
76
|
this.addError(error)
|
|
@@ -160,10 +160,9 @@ function limitDepth (input) {
|
|
|
160
160
|
input, output, depth,
|
|
161
161
|
} = queue.pop()
|
|
162
162
|
const nextDepth = depth + 1
|
|
163
|
-
for (const key
|
|
164
|
-
if (typeof input[key] === 'function') continue
|
|
165
|
-
|
|
163
|
+
for (const key of Object.keys(input)) {
|
|
166
164
|
let child = input[key]
|
|
165
|
+
if (typeof child === 'function') continue
|
|
167
166
|
|
|
168
167
|
if (isBSON(child)) {
|
|
169
168
|
child = typeof child.toJSON === 'function' ? child.toJSON() : '?'
|
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
TEST_SUITE,
|
|
40
40
|
TEST_HAS_DYNAMIC_NAME,
|
|
41
41
|
DYNAMIC_NAME_RE,
|
|
42
|
+
TEST_FINAL_STATUS,
|
|
42
43
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
43
44
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
44
45
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -319,6 +320,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
319
320
|
hasFailedAttemptToFixRetries,
|
|
320
321
|
isAtrRetry,
|
|
321
322
|
isModified,
|
|
323
|
+
finalStatus,
|
|
322
324
|
onDone,
|
|
323
325
|
}) => {
|
|
324
326
|
if (!span) return
|
|
@@ -379,6 +381,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
379
381
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
380
382
|
}
|
|
381
383
|
}
|
|
384
|
+
if (finalStatus) {
|
|
385
|
+
span.setTag(TEST_FINAL_STATUS, finalStatus)
|
|
386
|
+
}
|
|
382
387
|
for (const step of steps) {
|
|
383
388
|
const stepStartTime = step.startTime.getTime()
|
|
384
389
|
const stepSpan = this.tracer.startSpan('playwright.step', {
|
|
@@ -451,6 +456,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
451
456
|
)
|
|
452
457
|
|
|
453
458
|
span.setTag(TEST_STATUS, 'skip')
|
|
459
|
+
span.setTag(TEST_FINAL_STATUS, 'skip')
|
|
454
460
|
|
|
455
461
|
if (isNew) {
|
|
456
462
|
span.setTag(TEST_IS_NEW, 'true')
|
|
@@ -157,6 +157,13 @@ class RouterPlugin extends WebPlugin {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function isMoreSpecificThan (routeA, routeB) {
|
|
160
|
+
// Concrete paths beat catch-all wildcards (`/*splat`, `/api/*`) on the same
|
|
161
|
+
// request so that `/foo/bar` wins over `/foo/*splat` regardless of length.
|
|
162
|
+
if (routeA && routeB) {
|
|
163
|
+
const aWild = hasWildcard(routeA)
|
|
164
|
+
const bWild = hasWildcard(routeB)
|
|
165
|
+
if (aWild !== bWild) return !aWild
|
|
166
|
+
}
|
|
160
167
|
if (!routeIsRegex(routeA) && routeIsRegex(routeB)) {
|
|
161
168
|
return true
|
|
162
169
|
}
|
|
@@ -167,4 +174,10 @@ function routeIsRegex (route) {
|
|
|
167
174
|
return route.includes('(/')
|
|
168
175
|
}
|
|
169
176
|
|
|
177
|
+
function hasWildcard (route) {
|
|
178
|
+
// RegExp routes are encoded as `(/.../)` and may legitimately contain `*`,
|
|
179
|
+
// so only treat plain string patterns as wildcards.
|
|
180
|
+
return !routeIsRegex(route) && route.includes('*')
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
module.exports = RouterPlugin
|
|
@@ -30,9 +30,10 @@ if (!global._ddtrace) {
|
|
|
30
30
|
configurable: true,
|
|
31
31
|
writable: true,
|
|
32
32
|
})
|
|
33
|
-
|
|
34
|
-
global._ddtrace.default = global._ddtrace
|
|
35
|
-
global._ddtrace.tracer = global._ddtrace
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
module.exports = global._ddtrace
|
|
36
|
+
// Static aliases so cjs-module-lexer surfaces them as ESM named exports
|
|
37
|
+
// (`import { tracer } from 'dd-trace'`).
|
|
38
|
+
module.exports.tracer = global._ddtrace
|
|
39
|
+
module.exports.default = global._ddtrace
|
|
@@ -70,7 +70,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
70
70
|
constructor (tracer, config) {
|
|
71
71
|
super()
|
|
72
72
|
|
|
73
|
-
if (!config.apiKey || !config.
|
|
73
|
+
if (!config.apiKey || !config.DD_APP_KEY) {
|
|
74
74
|
log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
|
|
75
75
|
this.#initialized = false
|
|
76
76
|
return
|
|
@@ -78,7 +78,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
78
78
|
this.#tracer = tracer
|
|
79
79
|
this.#headers = {
|
|
80
80
|
'DD-API-KEY': config.apiKey,
|
|
81
|
-
'DD-APPLICATION-KEY': config.
|
|
81
|
+
'DD-APPLICATION-KEY': config.DD_APP_KEY,
|
|
82
82
|
'DD-AI-GUARD-VERSION': tracerVersion,
|
|
83
83
|
'DD-AI-GUARD-SOURCE': 'SDK',
|
|
84
84
|
'DD-AI-GUARD-LANGUAGE': 'nodejs',
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { LRUCache } = require('../../../../vendor/dist/lru-cache')
|
|
3
4
|
const log = require('../log')
|
|
5
|
+
const web = require('../plugins/util/web')
|
|
4
6
|
const blockedTemplates = require('./blocked_templates')
|
|
5
7
|
const { updateBlockFailureMetric } = require('./telemetry')
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
// Bounded by the LRU as defense-in-depth: getSpecificKey already keys on the
|
|
10
|
+
// resolved route (or the path with the query string stripped) so cardinality
|
|
11
|
+
// follows the routing table, not the URL space.
|
|
12
|
+
const SPECIFIC_ENDPOINT_CACHE_MAX = 16_384
|
|
13
|
+
const detectedSpecificEndpoints = new LRUCache({ max: SPECIFIC_ENDPOINT_CACHE_MAX })
|
|
8
14
|
|
|
9
15
|
const templateKeyword = '[security_response_id]'
|
|
10
16
|
|
|
@@ -38,12 +44,18 @@ const specificBlockingTypes = {
|
|
|
38
44
|
GRAPHQL: 'graphqlJson',
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
function getSpecificKey (
|
|
42
|
-
|
|
47
|
+
function getSpecificKey (req) {
|
|
48
|
+
const route = web.getContext(req)?.paths?.join('')
|
|
49
|
+
if (route) return `${req.method}+${route}`
|
|
50
|
+
|
|
51
|
+
// Strip the query string so unique parameters do not balloon the cache.
|
|
52
|
+
const url = req.originalUrl || req.url || ''
|
|
53
|
+
const queryStart = url.indexOf('?')
|
|
54
|
+
return `${req.method}+${queryStart === -1 ? url : url.slice(0, queryStart)}`
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
function addSpecificEndpoint (
|
|
46
|
-
detectedSpecificEndpoints
|
|
57
|
+
function addSpecificEndpoint (req, type) {
|
|
58
|
+
detectedSpecificEndpoints.set(getSpecificKey(req), type)
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
function getBlockWithRedirectData (actionParameters) {
|
|
@@ -65,7 +77,7 @@ function getBlockWithContentData (req, specificType, actionParameters) {
|
|
|
65
77
|
let type
|
|
66
78
|
let body
|
|
67
79
|
|
|
68
|
-
const specificBlockingType = specificType || detectedSpecificEndpoints
|
|
80
|
+
const specificBlockingType = specificType || detectedSpecificEndpoints.get(getSpecificKey(req))
|
|
69
81
|
if (specificBlockingType) {
|
|
70
82
|
const specificBlockingContent = getTemplate(specificBlockingType, actionParameters)
|
|
71
83
|
type = specificBlockingContent?.type
|
|
@@ -78,7 +78,7 @@ function enterInApolloRequest () {
|
|
|
78
78
|
// Set isInGraphqlRequest=true since this function only runs for GraphQL requests
|
|
79
79
|
// This works for both Apollo v4 (middleware) and v5 (HTTP server) contexts
|
|
80
80
|
requestData.isInGraphqlRequest = true
|
|
81
|
-
addSpecificEndpoint(req
|
|
81
|
+
addSpecificEndpoint(req, specificBlockingTypes.GRAPHQL)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
const { storage } = require('../../datadog-core')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* - OpenTelemetry Baggage API: https://opentelemetry.io/docs/specs/otel/baggage/api/
|
|
6
|
+
* In-process baggage map stored in async local storage. Frozen on every write.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* @see https://opentelemetry.io/docs/specs/otel/baggage/api/
|
|
10
9
|
* @typedef {import('../../datadog-core/src/storage').Store<string>} BaggageStore
|
|
11
10
|
*/
|
|
12
11
|
|
|
@@ -18,6 +17,8 @@ const baggageStorage =
|
|
|
18
17
|
/** @type {unknown} */ (storage('baggage'))
|
|
19
18
|
)
|
|
20
19
|
|
|
20
|
+
const EMPTY_STORE = Object.freeze(/** @type {BaggageStore} */ ({}))
|
|
21
|
+
|
|
21
22
|
// TODO: Implement metadata https://opentelemetry.io/docs/specs/otel/baggage/api/#set-value
|
|
22
23
|
/**
|
|
23
24
|
* @param {string} key
|
|
@@ -25,12 +26,11 @@ const baggageStorage =
|
|
|
25
26
|
* @param {object} [metadata] Not used yet
|
|
26
27
|
*/
|
|
27
28
|
function setBaggageItem (key, value, metadata) {
|
|
29
|
+
const store = baggageStorage.getStore()
|
|
28
30
|
if (typeof key !== 'string' || typeof value !== 'string' || key === '') {
|
|
29
|
-
return
|
|
31
|
+
return store ?? EMPTY_STORE
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
const store = baggageStorage.getStore()
|
|
33
|
-
const newStore = { ...store, [key]: value }
|
|
33
|
+
const newStore = Object.freeze({ ...store, [key]: value })
|
|
34
34
|
baggageStorage.enterWith(newStore)
|
|
35
35
|
return newStore
|
|
36
36
|
}
|
|
@@ -44,27 +44,40 @@ function getBaggageItem (key) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function getAllBaggageItems () {
|
|
47
|
-
return baggageStorage.getStore() ??
|
|
47
|
+
return baggageStorage.getStore() ?? EMPTY_STORE
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* @param {string} keyToRemove
|
|
51
|
+
* @param {string} keyToRemove No-op for non-string or empty keys.
|
|
52
52
|
*/
|
|
53
53
|
function removeBaggageItem (keyToRemove) {
|
|
54
|
-
const store = baggageStorage.getStore() ??
|
|
54
|
+
const store = baggageStorage.getStore() ?? EMPTY_STORE
|
|
55
|
+
if (typeof keyToRemove !== 'string' || keyToRemove === '') {
|
|
56
|
+
return store
|
|
57
|
+
}
|
|
55
58
|
const { [keyToRemove]: _, ...newBaggage } = store
|
|
59
|
+
Object.freeze(newBaggage)
|
|
56
60
|
baggageStorage.enterWith(newBaggage)
|
|
57
61
|
return newBaggage
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
function removeAllBaggageItems () {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
baggageStorage.enterWith(EMPTY_STORE)
|
|
66
|
+
return EMPTY_STORE
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {BaggageStore} items Frozen in place; do not mutate after.
|
|
71
|
+
*/
|
|
72
|
+
function setAllBaggageItems (items) {
|
|
73
|
+
Object.freeze(items)
|
|
74
|
+
baggageStorage.enterWith(items)
|
|
75
|
+
return items
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
module.exports = {
|
|
67
79
|
setBaggageItem,
|
|
80
|
+
setAllBaggageItems,
|
|
68
81
|
getBaggageItem,
|
|
69
82
|
getAllBaggageItems,
|
|
70
83
|
removeBaggageItem,
|
|
@@ -234,7 +234,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
234
234
|
testManagementAttemptToFixRetries ?? this._config.testManagementAttemptToFixRetries,
|
|
235
235
|
isImpactedTestsEnabled: isImpactedTestsEnabled && this._config.isImpactedTestsEnabled,
|
|
236
236
|
isCoverageReportUploadEnabled,
|
|
237
|
-
|
|
237
|
+
DD_TEST_TIA_KEEP_COV_CONFIG: this._config.DD_TEST_TIA_KEEP_COV_CONFIG,
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|