dd-trace 5.99.1 → 5.101.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/index.d.ts +14 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- 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/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +146 -90
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- 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 +5 -4
- package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- 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/reporter.js +4 -1
- package/packages/dd-trace/src/baggage.js +10 -0
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +7 -60
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +17 -15
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +6 -3
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +3 -3
- package/packages/dd-trace/src/encode/0.5.js +2 -2
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +5 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +80 -5
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
- package/packages/dd-trace/src/opentelemetry/span.js +42 -108
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
- package/packages/dd-trace/src/opentracing/span.js +58 -49
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- 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 +3 -3
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +1 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/packages/dd-trace/src/util.js +17 -0
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -217,6 +217,8 @@ const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
|
|
|
217
217
|
const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
|
|
218
218
|
const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
|
|
219
219
|
|
|
220
|
+
const MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS = 10
|
|
221
|
+
|
|
220
222
|
// Impacted tests
|
|
221
223
|
const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
|
|
222
224
|
const BASE_LIKE_BRANCH_FILTER = /^(main|master|preprod|prod|dev|development|trunk|release\/.*|hotfix\/.*)$/
|
|
@@ -358,7 +360,14 @@ module.exports = {
|
|
|
358
360
|
GIT_REPOSITORY_URL,
|
|
359
361
|
DYNAMIC_NAME_RE,
|
|
360
362
|
collectDynamicNamesFromTraces,
|
|
363
|
+
collectTestOptimizationSummariesFromTraces,
|
|
361
364
|
logDynamicNamesWarning,
|
|
365
|
+
recordAttemptToFixExecution,
|
|
366
|
+
collectAttemptToFixExecutionsFromTraces,
|
|
367
|
+
formatAttemptToFixSummary,
|
|
368
|
+
logAttemptToFixTestExecution,
|
|
369
|
+
formatDynamicNamesSummary,
|
|
370
|
+
logTestOptimizationSummary,
|
|
362
371
|
}
|
|
363
372
|
|
|
364
373
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -1327,23 +1336,149 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1327
1336
|
}
|
|
1328
1337
|
|
|
1329
1338
|
/**
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1339
|
+
* @typedef {object} AttemptToFixExecutionResult
|
|
1340
|
+
* @property {string} name
|
|
1341
|
+
* @property {number} executions
|
|
1342
|
+
* @property {number} failedCount
|
|
1343
|
+
* @property {boolean} isDisabled
|
|
1344
|
+
* @property {boolean} isQuarantined
|
|
1345
|
+
*/
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* @typedef {Map<string, AttemptToFixExecutionResult>} AttemptToFixExecutions
|
|
1349
|
+
*/
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Formats a test name for user-facing Test Optimization summaries.
|
|
1353
|
+
*
|
|
1354
|
+
* @param {string | undefined} testSuite
|
|
1355
|
+
* @param {string} testName
|
|
1356
|
+
* @returns {string}
|
|
1357
|
+
*/
|
|
1358
|
+
function formatTestOptimizationName (testSuite, testName) {
|
|
1359
|
+
return testSuite ? `${testSuite} › ${testName}` : testName
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* Renders a bounded bullet list for Test Optimization summaries.
|
|
1364
|
+
*
|
|
1365
|
+
* @param {Array<{ text: string, suffix?: string }>} items
|
|
1366
|
+
* @returns {string}
|
|
1367
|
+
*/
|
|
1368
|
+
function formatTestOptimizationList (items) {
|
|
1369
|
+
const shown = items.slice(0, MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS)
|
|
1370
|
+
const more = items.length - shown.length
|
|
1371
|
+
const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
|
|
1372
|
+
|
|
1373
|
+
return shown.map(({ text, suffix }) => ` • ${text}${suffix ? ` (${suffix})` : ''}`).join('\n') + moreSuffix
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Logs a compact message when an attempt-to-fix test execution starts.
|
|
1378
|
+
*
|
|
1379
|
+
* @param {string | undefined} testSuite
|
|
1380
|
+
* @param {string} testName
|
|
1381
|
+
* @param {Set<string>} [loggedAttemptToFixTests]
|
|
1382
|
+
*/
|
|
1383
|
+
function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTests) {
|
|
1384
|
+
if (!testName) return
|
|
1385
|
+
|
|
1386
|
+
const name = formatTestOptimizationName(testSuite, testName)
|
|
1387
|
+
if (loggedAttemptToFixTests) {
|
|
1388
|
+
if (loggedAttemptToFixTests.has(name)) return
|
|
1389
|
+
|
|
1390
|
+
loggedAttemptToFixTests.add(name)
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// eslint-disable-next-line no-console -- Intentional user-facing attempt-to-fix progress report
|
|
1394
|
+
console.warn(`Datadog Test Optimization: attempting to fix ${name}`)
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Records a single attempt-to-fix execution for the end-of-session user summary.
|
|
1399
|
+
*
|
|
1400
|
+
* @param {AttemptToFixExecutions} attemptToFixExecutions
|
|
1401
|
+
* @param {{
|
|
1402
|
+
* testSuite?: string,
|
|
1403
|
+
* testName: string,
|
|
1404
|
+
* status: string,
|
|
1405
|
+
* isDisabled?: boolean,
|
|
1406
|
+
* isQuarantined?: boolean
|
|
1407
|
+
* }} execution
|
|
1408
|
+
*/
|
|
1409
|
+
function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
|
|
1410
|
+
if (!execution?.testName) return
|
|
1411
|
+
|
|
1412
|
+
const { testSuite, testName, status, isDisabled, isQuarantined } = execution
|
|
1413
|
+
const name = formatTestOptimizationName(testSuite, testName)
|
|
1414
|
+
let result = attemptToFixExecutions.get(name)
|
|
1415
|
+
|
|
1416
|
+
if (!result) {
|
|
1417
|
+
result = {
|
|
1418
|
+
name,
|
|
1419
|
+
executions: 0,
|
|
1420
|
+
failedCount: 0,
|
|
1421
|
+
isDisabled: false,
|
|
1422
|
+
isQuarantined: false,
|
|
1423
|
+
}
|
|
1424
|
+
attemptToFixExecutions.set(name, result)
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
result.executions++
|
|
1428
|
+
result.isDisabled = result.isDisabled || !!isDisabled
|
|
1429
|
+
result.isQuarantined = result.isQuarantined || !!isQuarantined
|
|
1430
|
+
|
|
1431
|
+
if (status === 'fail') {
|
|
1432
|
+
result.failedCount++
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function collectDynamicNameFromTraceSpan (span, newTestsWithDynamicNames) {
|
|
1437
|
+
const meta = span.meta
|
|
1438
|
+
if (meta?.[TEST_HAS_DYNAMIC_NAME] !== 'true') return
|
|
1439
|
+
|
|
1440
|
+
const suite = meta[TEST_SUITE]
|
|
1441
|
+
const name = meta[TEST_NAME]
|
|
1442
|
+
if (suite && name) {
|
|
1443
|
+
newTestsWithDynamicNames.add(`${suite} › ${name}`)
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions) {
|
|
1448
|
+
const meta = span.meta
|
|
1449
|
+
if (meta?.[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] !== 'true') return
|
|
1450
|
+
|
|
1451
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
1452
|
+
testSuite: meta[TEST_SUITE],
|
|
1453
|
+
testName: meta[TEST_NAME],
|
|
1454
|
+
status: meta[TEST_STATUS],
|
|
1455
|
+
isDisabled: meta[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
1456
|
+
isQuarantined: meta[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
|
|
1457
|
+
})
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Scans serialized worker trace payloads and populates Test Optimization summary data.
|
|
1462
|
+
* Silently ignores parse errors.
|
|
1332
1463
|
*
|
|
1333
1464
|
* @param {string} data - JSON-serialized traces from a worker
|
|
1334
|
-
* @param {
|
|
1465
|
+
* @param {{
|
|
1466
|
+
* newTestsWithDynamicNames?: Set<string>,
|
|
1467
|
+
* attemptToFixExecutions?: AttemptToFixExecutions
|
|
1468
|
+
* }} summaries
|
|
1335
1469
|
*/
|
|
1336
|
-
function
|
|
1470
|
+
function collectTestOptimizationSummariesFromTraces (data, summaries) {
|
|
1471
|
+
const { newTestsWithDynamicNames, attemptToFixExecutions } = summaries
|
|
1472
|
+
|
|
1337
1473
|
try {
|
|
1338
1474
|
const traces = JSON.parse(data)
|
|
1339
1475
|
for (const trace of traces) {
|
|
1340
1476
|
for (const span of trace) {
|
|
1341
|
-
if (
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
}
|
|
1477
|
+
if (newTestsWithDynamicNames) {
|
|
1478
|
+
collectDynamicNameFromTraceSpan(span, newTestsWithDynamicNames)
|
|
1479
|
+
}
|
|
1480
|
+
if (attemptToFixExecutions) {
|
|
1481
|
+
collectAttemptToFixExecutionFromTraceSpan(span, attemptToFixExecutions)
|
|
1347
1482
|
}
|
|
1348
1483
|
}
|
|
1349
1484
|
}
|
|
@@ -1353,33 +1488,164 @@ function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
|
|
|
1353
1488
|
}
|
|
1354
1489
|
|
|
1355
1490
|
/**
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1491
|
+
* Scans serialized worker trace payloads for tests tagged with TEST_HAS_DYNAMIC_NAME
|
|
1492
|
+
* and populates the provided Set. Silently ignores parse errors.
|
|
1358
1493
|
*
|
|
1359
|
-
* @param {
|
|
1494
|
+
* @param {string} data - JSON-serialized traces from a worker
|
|
1495
|
+
* @param {Set<string>} newTestsWithDynamicNames - Set to populate with "suite › name" strings
|
|
1360
1496
|
*/
|
|
1361
|
-
function
|
|
1362
|
-
|
|
1497
|
+
function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
|
|
1498
|
+
collectTestOptimizationSummariesFromTraces(data, { newTestsWithDynamicNames })
|
|
1499
|
+
}
|
|
1363
1500
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1501
|
+
/**
|
|
1502
|
+
* Scans serialized worker trace payloads for attempt-to-fix test spans.
|
|
1503
|
+
*
|
|
1504
|
+
* @param {string} data - JSON-serialized traces from a worker
|
|
1505
|
+
* @param {AttemptToFixExecutions} attemptToFixExecutions
|
|
1506
|
+
*/
|
|
1507
|
+
function collectAttemptToFixExecutionsFromTraces (data, attemptToFixExecutions) {
|
|
1508
|
+
collectTestOptimizationSummariesFromTraces(data, { attemptToFixExecutions })
|
|
1509
|
+
}
|
|
1370
1510
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1511
|
+
function getAttemptToFixManagementNotes (result) {
|
|
1512
|
+
const notes = []
|
|
1513
|
+
|
|
1514
|
+
if (result.isDisabled) {
|
|
1515
|
+
notes.push('Test was marked as disabled but was run because it is attempt to fix.')
|
|
1516
|
+
}
|
|
1517
|
+
if (result.isQuarantined) {
|
|
1518
|
+
notes.push('Test was marked as quarantined but was not quarantined because it is attempt to fix.')
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
return notes
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function hasAttemptToFixManagementNotes (result) {
|
|
1525
|
+
return result.isDisabled || result.isQuarantined
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function addAttemptToFixResultLine (lines, result) {
|
|
1529
|
+
lines.push(` • ${result.name}`)
|
|
1530
|
+
|
|
1531
|
+
for (const note of getAttemptToFixManagementNotes(result)) {
|
|
1532
|
+
lines.push(` ${note}`)
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Formats the attempt-to-fix end-of-session summary.
|
|
1538
|
+
*
|
|
1539
|
+
* @param {AttemptToFixExecutions} attemptToFixExecutions
|
|
1540
|
+
* @returns {string}
|
|
1541
|
+
*/
|
|
1542
|
+
function formatAttemptToFixSummary (attemptToFixExecutions) {
|
|
1543
|
+
if (attemptToFixExecutions.size === 0) return ''
|
|
1544
|
+
|
|
1545
|
+
const results = [...attemptToFixExecutions.values()]
|
|
1546
|
+
const failedResults = results.filter(result => result.failedCount > 0)
|
|
1547
|
+
const totalExecutions = results.reduce((total, result) => total + result.executions, 0)
|
|
1548
|
+
|
|
1549
|
+
if (failedResults.length === 0) {
|
|
1550
|
+
const lines = [
|
|
1551
|
+
`Attempt to fix passed: all ${totalExecutions} execution(s) passed for ${results.length} test(s).`,
|
|
1552
|
+
]
|
|
1553
|
+
|
|
1554
|
+
for (const result of results) {
|
|
1555
|
+
if (hasAttemptToFixManagementNotes(result)) {
|
|
1556
|
+
addAttemptToFixResultLine(lines, result)
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
return lines.join('\n')
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const totalFailedExecutions = failedResults.reduce(
|
|
1564
|
+
(total, result) => total + result.failedCount,
|
|
1565
|
+
0
|
|
1566
|
+
)
|
|
1567
|
+
const lines = [
|
|
1568
|
+
`Attempt to fix failed: ${totalFailedExecutions} of ${totalExecutions} execution(s) failed ` +
|
|
1569
|
+
`across ${failedResults.length} of ${results.length} test(s).`,
|
|
1570
|
+
]
|
|
1571
|
+
|
|
1572
|
+
for (const result of failedResults) {
|
|
1573
|
+
addAttemptToFixResultLine(lines, result)
|
|
1574
|
+
}
|
|
1575
|
+
for (const result of results) {
|
|
1576
|
+
if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
|
|
1577
|
+
addAttemptToFixResultLine(lines, result)
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
return lines.join('\n')
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Formats the dynamic-name warning section of the Test Optimization summary.
|
|
1586
|
+
*
|
|
1587
|
+
* @param {Set<string>} newTestsWithDynamicNames
|
|
1588
|
+
* @returns {string}
|
|
1589
|
+
*/
|
|
1590
|
+
function formatDynamicNamesSummary (newTestsWithDynamicNames) {
|
|
1591
|
+
if (newTestsWithDynamicNames.size === 0) return ''
|
|
1592
|
+
|
|
1593
|
+
const items = [...newTestsWithDynamicNames].map(name => ({ text: name }))
|
|
1594
|
+
return (
|
|
1375
1595
|
`${newTestsWithDynamicNames.size} test(s) detected as new but their names contain ` +
|
|
1376
1596
|
'dynamic data (timestamps, UUIDs, etc.).\n' +
|
|
1377
1597
|
'Tests with changing names are always treated as new on every run, ' +
|
|
1378
1598
|
'causing unnecessary Early Flake Detection retries and preventing correct new test detection.\n' +
|
|
1379
1599
|
'Consider using stable, deterministic test names.\n\n' +
|
|
1380
|
-
|
|
1600
|
+
formatTestOptimizationList(items)
|
|
1381
1601
|
)
|
|
1382
|
-
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/**
|
|
1605
|
+
* Logs a single Test Optimization session summary.
|
|
1606
|
+
*
|
|
1607
|
+
* @param {{
|
|
1608
|
+
* attemptToFixExecutions?: AttemptToFixExecutions,
|
|
1609
|
+
* newTestsWithDynamicNames?: Set<string>,
|
|
1610
|
+
* extraSections?: string[]
|
|
1611
|
+
* }} summary
|
|
1612
|
+
*/
|
|
1613
|
+
function logTestOptimizationSummary (summary) {
|
|
1614
|
+
const { attemptToFixExecutions, newTestsWithDynamicNames, extraSections = [] } = summary
|
|
1615
|
+
const sections = []
|
|
1616
|
+
const attemptToFixSummary = attemptToFixExecutions
|
|
1617
|
+
? formatAttemptToFixSummary(attemptToFixExecutions)
|
|
1618
|
+
: ''
|
|
1619
|
+
const dynamicNamesSummary = newTestsWithDynamicNames
|
|
1620
|
+
? formatDynamicNamesSummary(newTestsWithDynamicNames)
|
|
1621
|
+
: ''
|
|
1622
|
+
|
|
1623
|
+
if (attemptToFixSummary) sections.push(attemptToFixSummary)
|
|
1624
|
+
sections.push(...extraSections.filter(Boolean))
|
|
1625
|
+
if (dynamicNamesSummary) sections.push(dynamicNamesSummary)
|
|
1626
|
+
|
|
1627
|
+
if (sections.length === 0) return
|
|
1628
|
+
|
|
1629
|
+
const line = '-'.repeat(50)
|
|
1630
|
+
// eslint-disable-next-line no-console -- Intentional user-facing session summary
|
|
1631
|
+
console.warn(`\n${line}\nDatadog Test Optimization\n${line}\n${sections.join('\n\n')}\n`)
|
|
1632
|
+
|
|
1633
|
+
if (attemptToFixExecutions) {
|
|
1634
|
+
attemptToFixExecutions.clear()
|
|
1635
|
+
}
|
|
1636
|
+
if (newTestsWithDynamicNames) {
|
|
1637
|
+
newTestsWithDynamicNames.clear()
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* Logs a "Datadog Test Optimization" warning about new tests with dynamic names.
|
|
1643
|
+
* Clears the Set after logging. No-op if the Set is empty.
|
|
1644
|
+
*
|
|
1645
|
+
* @param {Set<string>} newTestsWithDynamicNames
|
|
1646
|
+
*/
|
|
1647
|
+
function logDynamicNamesWarning (newTestsWithDynamicNames) {
|
|
1648
|
+
logTestOptimizationSummary({ newTestsWithDynamicNames })
|
|
1383
1649
|
}
|
|
1384
1650
|
|
|
1385
1651
|
function isModifiedTest (testPath, testStartLine, testEndLine, modifiedFiles, testFramework) {
|
|
@@ -148,9 +148,8 @@ class PrioritySampler {
|
|
|
148
148
|
update (rates) {
|
|
149
149
|
const samplers = {}
|
|
150
150
|
|
|
151
|
-
for (const key
|
|
152
|
-
|
|
153
|
-
samplers[key] = new Sampler(rate)
|
|
151
|
+
for (const key of Object.keys(rates)) {
|
|
152
|
+
samplers[key] = new Sampler(rates[key])
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler
|
|
@@ -334,7 +333,10 @@ class PrioritySampler {
|
|
|
334
333
|
if (!trace.tags[DECISION_MAKER_KEY]) {
|
|
335
334
|
trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
|
|
336
335
|
}
|
|
337
|
-
} else {
|
|
336
|
+
} else if (DECISION_MAKER_KEY in trace.tags) {
|
|
337
|
+
// Guard the `delete` so the common drop path doesn't pay the V8
|
|
338
|
+
// dictionary-mode transition unless a prior keep decision actually
|
|
339
|
+
// set the tag.
|
|
338
340
|
delete trace.tags[DECISION_MAKER_KEY]
|
|
339
341
|
}
|
|
340
342
|
}
|
|
@@ -4,6 +4,7 @@ const path = require('path')
|
|
|
4
4
|
const { pathToFileURL } = require('url')
|
|
5
5
|
|
|
6
6
|
const satisfies = require('../../../../vendor/dist/semifies')
|
|
7
|
+
const getGitMetadata = require('../git_metadata')
|
|
7
8
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
8
9
|
const { getIsAzureFunction } = require('../serverless')
|
|
9
10
|
const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
|
|
@@ -38,10 +39,10 @@ class Config {
|
|
|
38
39
|
...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
this.tags[GIT_REPOSITORY_URL] =
|
|
44
|
-
this.tags[GIT_COMMIT_SHA] =
|
|
42
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(options)
|
|
43
|
+
if (repositoryUrl && commitSHA) {
|
|
44
|
+
this.tags[GIT_REPOSITORY_URL] = repositoryUrl
|
|
45
|
+
this.tags[GIT_COMMIT_SHA] = commitSHA
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// Normalize from seconds to milliseconds. Default must be longer than a minute.
|
|
@@ -14,12 +14,12 @@ class SSIHeuristics {
|
|
|
14
14
|
* @param {import('../config/config-base')} config - Tracer configuration
|
|
15
15
|
*/
|
|
16
16
|
constructor (config) {
|
|
17
|
-
const longLivedThreshold = config.
|
|
17
|
+
const longLivedThreshold = config.DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD || DEFAULT_LONG_LIVED_THRESHOLD
|
|
18
18
|
if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
|
|
19
19
|
this.longLivedThreshold = DEFAULT_LONG_LIVED_THRESHOLD
|
|
20
20
|
log.warn(
|
|
21
21
|
'Invalid SSIHeuristics.longLivedThreshold value: %s. Using default value:',
|
|
22
|
-
config.
|
|
22
|
+
config.DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD,
|
|
23
23
|
DEFAULT_LONG_LIVED_THRESHOLD
|
|
24
24
|
)
|
|
25
25
|
} else {
|
|
@@ -113,11 +113,11 @@ class Tracer extends NoopProxy {
|
|
|
113
113
|
const propagationHash = require('./propagation-hash')
|
|
114
114
|
propagationHash.configure(config)
|
|
115
115
|
|
|
116
|
-
if (config.
|
|
116
|
+
if (config.DD_CRASHTRACKING_ENABLED) {
|
|
117
117
|
require('./crashtracking').start(config)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
if (config.
|
|
120
|
+
if (config.DD_HEAP_SNAPSHOT_COUNT > 0) {
|
|
121
121
|
require('./heap_snapshots').start(config)
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -229,7 +229,7 @@ class Tracer extends NoopProxy {
|
|
|
229
229
|
initializeOpenTelemetryLogs(config)
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
if (config.
|
|
232
|
+
if (config.DD_METRICS_OTEL_ENABLED) {
|
|
233
233
|
const { initializeOpenTelemetryMetrics } = require('./opentelemetry/metrics')
|
|
234
234
|
initializeOpenTelemetryMetrics(config)
|
|
235
235
|
}
|
|
@@ -5,6 +5,7 @@ const tracerVersion = require('../../../../package.json').version
|
|
|
5
5
|
const request = require('../exporters/common/request')
|
|
6
6
|
const log = require('../log')
|
|
7
7
|
const { getExtraServices } = require('../service-naming/extra-services')
|
|
8
|
+
const getGitMetadata = require('../git_metadata')
|
|
8
9
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
9
10
|
const tagger = require('../tagger')
|
|
10
11
|
const { getAgentUrl } = require('../agent/url')
|
|
@@ -37,11 +38,12 @@ class RemoteConfig {
|
|
|
37
38
|
'_dd.rc.client_id': clientId,
|
|
38
39
|
})
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
42
|
+
const tags = repositoryUrl
|
|
41
43
|
? {
|
|
42
44
|
...config.tags,
|
|
43
|
-
[GIT_REPOSITORY_URL]:
|
|
44
|
-
[GIT_COMMIT_SHA]:
|
|
45
|
+
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
46
|
+
[GIT_COMMIT_SHA]: commitSHA,
|
|
45
47
|
}
|
|
46
48
|
: config.tags
|
|
47
49
|
|
|
@@ -42,7 +42,7 @@ module.exports = {
|
|
|
42
42
|
this.stop()
|
|
43
43
|
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
44
44
|
|
|
45
|
-
if (config.
|
|
45
|
+
if (config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) {
|
|
46
46
|
for (const tag of processTags.tagsArray) {
|
|
47
47
|
clientConfig.tags.push(tag)
|
|
48
48
|
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const tags = require('../../../ext/tags')
|
|
4
4
|
const constants = require('./constants')
|
|
5
|
+
const {
|
|
6
|
+
MAX_META_KEY_LENGTH,
|
|
7
|
+
MAX_META_VALUE_LENGTH,
|
|
8
|
+
MAX_METRIC_KEY_LENGTH,
|
|
9
|
+
} = require('./encode/tags-processors')
|
|
5
10
|
const id = require('./id')
|
|
6
11
|
const { isError } = require('./util')
|
|
7
12
|
const { registerExtraService } = require('./service-naming/extra-services')
|
|
@@ -33,6 +38,23 @@ const map = {
|
|
|
33
38
|
'resource.name': 'resource',
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} FormattedSpan
|
|
43
|
+
* @property {import('./id').Identifier} trace_id
|
|
44
|
+
* @property {import('./id').Identifier} span_id
|
|
45
|
+
* @property {import('./id').Identifier} parent_id
|
|
46
|
+
* @property {string} name
|
|
47
|
+
* @property {string} resource
|
|
48
|
+
* @property {number} error
|
|
49
|
+
* @property {Record<string, string>} meta
|
|
50
|
+
* @property {Record<string, number>} metrics
|
|
51
|
+
* @property {Record<string, unknown> | undefined} meta_struct
|
|
52
|
+
* @property {number} start
|
|
53
|
+
* @property {number} duration
|
|
54
|
+
* @property {Array} links
|
|
55
|
+
* @property {Array<{ name: string, time_unix_nano: number, attributes?: Record<string, string> }>} [span_events]
|
|
56
|
+
*/
|
|
57
|
+
|
|
36
58
|
function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = false) {
|
|
37
59
|
const formatted = formatSpan(span)
|
|
38
60
|
|
|
@@ -71,12 +93,15 @@ function setSingleSpanIngestionTags (span, options) {
|
|
|
71
93
|
addTag({}, span.metrics, SPAN_SAMPLING_MAX_PER_SECOND, options.maxPerSecond)
|
|
72
94
|
}
|
|
73
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @param {FormattedSpan} formattedSpan
|
|
98
|
+
* @param {import('./opentracing/span')} span
|
|
99
|
+
*/
|
|
74
100
|
function extractSpanLinks (formattedSpan, span) {
|
|
75
101
|
if (!span._links?.length) {
|
|
76
102
|
return
|
|
77
103
|
}
|
|
78
|
-
const links = span._links.map(
|
|
79
|
-
const { context, attributes } = link
|
|
104
|
+
const links = span._links.map(({ context, attributes }) => {
|
|
80
105
|
const formattedLink = {
|
|
81
106
|
trace_id: context.toTraceId(true),
|
|
82
107
|
span_id: context.toSpanId(true),
|
|
@@ -90,21 +115,28 @@ function extractSpanLinks (formattedSpan, span) {
|
|
|
90
115
|
|
|
91
116
|
return formattedLink
|
|
92
117
|
})
|
|
93
|
-
|
|
118
|
+
let serialized = JSON.stringify(links)
|
|
119
|
+
if (serialized.length > MAX_META_VALUE_LENGTH) {
|
|
120
|
+
serialized = `${serialized.slice(0, MAX_META_VALUE_LENGTH)}...`
|
|
121
|
+
}
|
|
122
|
+
formattedSpan.meta['_dd.span_links'] = serialized
|
|
94
123
|
}
|
|
95
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @param {FormattedSpan} formattedSpan
|
|
127
|
+
* @param {import('./opentracing/span')} span
|
|
128
|
+
*/
|
|
96
129
|
function extractSpanEvents (formattedSpan, span) {
|
|
97
130
|
if (!span._events?.length) {
|
|
98
131
|
return
|
|
99
132
|
}
|
|
100
|
-
|
|
133
|
+
formattedSpan.span_events = span._events.map(event => {
|
|
101
134
|
return {
|
|
102
135
|
name: event.name,
|
|
103
136
|
time_unix_nano: Math.round(event.startTime * 1e6),
|
|
104
137
|
attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined,
|
|
105
138
|
}
|
|
106
139
|
})
|
|
107
|
-
formattedSpan.span_events = events
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
function extractTags (formattedSpan, span) {
|
|
@@ -225,13 +257,25 @@ function extractError (formattedSpan, error) {
|
|
|
225
257
|
function addTag (meta, metrics, key, value, nested) {
|
|
226
258
|
switch (typeof value) {
|
|
227
259
|
case 'string':
|
|
260
|
+
if (key.length > MAX_META_KEY_LENGTH) {
|
|
261
|
+
key = `${key.slice(0, MAX_META_KEY_LENGTH)}...`
|
|
262
|
+
}
|
|
263
|
+
if (value.length > MAX_META_VALUE_LENGTH) {
|
|
264
|
+
value = `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
|
|
265
|
+
}
|
|
228
266
|
meta[key] = value
|
|
229
267
|
break
|
|
230
268
|
case 'number':
|
|
231
269
|
if (Number.isNaN(value)) break
|
|
270
|
+
if (key.length > MAX_METRIC_KEY_LENGTH) {
|
|
271
|
+
key = `${key.slice(0, MAX_METRIC_KEY_LENGTH)}...`
|
|
272
|
+
}
|
|
232
273
|
metrics[key] = value
|
|
233
274
|
break
|
|
234
275
|
case 'boolean':
|
|
276
|
+
if (key.length > MAX_METRIC_KEY_LENGTH) {
|
|
277
|
+
key = `${key.slice(0, MAX_METRIC_KEY_LENGTH)}...`
|
|
278
|
+
}
|
|
235
279
|
metrics[key] = value ? 1 : 0
|
|
236
280
|
break
|
|
237
281
|
default:
|
|
@@ -240,6 +284,9 @@ function addTag (meta, metrics, key, value, nested) {
|
|
|
240
284
|
// Special case for Node.js Buffer and URL
|
|
241
285
|
// TODO(BridgeAR)[31.03.2025]: Figure out if all typed arrays should be treated as buffers.
|
|
242
286
|
if (isNodeBuffer(value) || isUrl(value)) {
|
|
287
|
+
if (key.length > MAX_METRIC_KEY_LENGTH) {
|
|
288
|
+
key = `${key.slice(0, MAX_METRIC_KEY_LENGTH)}...`
|
|
289
|
+
}
|
|
243
290
|
metrics[key] = value.toString()
|
|
244
291
|
} else if (!Array.isArray(value) && !nested) {
|
|
245
292
|
for (const [prop, val] of Object.entries(value)) {
|
|
@@ -25,7 +25,7 @@ class SpanProcessor {
|
|
|
25
25
|
this._spanSampler = new SpanSampler(config.sampler)
|
|
26
26
|
this._gitMetadataTagger = new GitMetadataTagger(config)
|
|
27
27
|
|
|
28
|
-
this._processTags = config.
|
|
28
|
+
this._processTags = config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED
|
|
29
29
|
? processTags.serialized
|
|
30
30
|
: false
|
|
31
31
|
}
|
|
@@ -158,10 +158,6 @@ class SpanProcessor {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
for (const span of trace.finished) {
|
|
162
|
-
span.context()._tags = {}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
161
|
trace.started = active
|
|
166
162
|
trace.finished = []
|
|
167
163
|
}
|
|
@@ -163,12 +163,14 @@ function getProducts (config) {
|
|
|
163
163
|
* @param {import('../config/config-base')} config
|
|
164
164
|
*/
|
|
165
165
|
function getInstallSignature (config) {
|
|
166
|
-
const
|
|
167
|
-
|
|
166
|
+
const id = config.DD_INSTRUMENTATION_INSTALL_ID
|
|
167
|
+
const time = config.DD_INSTRUMENTATION_INSTALL_TIME
|
|
168
|
+
const type = config.DD_INSTRUMENTATION_INSTALL_TYPE
|
|
169
|
+
if (id || time || type) {
|
|
168
170
|
return {
|
|
169
|
-
install_id:
|
|
170
|
-
install_time:
|
|
171
|
-
install_type:
|
|
171
|
+
install_id: id,
|
|
172
|
+
install_time: time,
|
|
173
|
+
install_type: type,
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
}
|
|
@@ -14,7 +14,7 @@ function storeConfig (config) {
|
|
|
14
14
|
const { containerId } = require('./exporters/common/docker')
|
|
15
15
|
const processTags = require('./process-tags')
|
|
16
16
|
|
|
17
|
-
const processTagsSerialized = config.
|
|
17
|
+
const processTagsSerialized = config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED
|
|
18
18
|
? (processTags.serialized || null)
|
|
19
19
|
: null
|
|
20
20
|
|