dd-trace 5.36.0 → 5.37.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 -1
- package/index.d.ts +5 -0
- package/loader-hook.mjs +0 -4
- package/package.json +14 -13
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +103 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-cucumber/src/index.js +20 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-mocha/src/index.js +22 -3
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-openai/src/tracing.js +1 -2
- package/packages/datadog-plugin-playwright/src/index.js +31 -5
- package/packages/datadog-plugin-vitest/src/index.js +25 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
- package/packages/dd-trace/src/appsec/iast/index.js +2 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/config.js +16 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/opentracing/span.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +6 -4
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/memwatch/package.json +0 -9
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const request = require('../../exporters/common/request')
|
|
2
|
+
const id = require('../../id')
|
|
3
|
+
|
|
4
|
+
function getQuarantinedTests ({
|
|
5
|
+
url,
|
|
6
|
+
isEvpProxy,
|
|
7
|
+
evpProxyPrefix,
|
|
8
|
+
isGzipCompatible,
|
|
9
|
+
repositoryUrl
|
|
10
|
+
}, done) {
|
|
11
|
+
const options = {
|
|
12
|
+
path: '/api/v2/test/libraries/test-management/tests',
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json'
|
|
16
|
+
},
|
|
17
|
+
timeout: 20000,
|
|
18
|
+
url
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isGzipCompatible) {
|
|
22
|
+
options.headers['accept-encoding'] = 'gzip'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isEvpProxy) {
|
|
26
|
+
options.path = `${evpProxyPrefix}/api/v2/test/libraries/test-management/tests`
|
|
27
|
+
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
28
|
+
} else {
|
|
29
|
+
const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
return done(new Error('Quarantined tests were not fetched because Datadog API key is not defined.'))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
options.headers['dd-api-key'] = apiKey
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data = JSON.stringify({
|
|
38
|
+
data: {
|
|
39
|
+
id: id().toString(10),
|
|
40
|
+
type: 'ci_app_libraries_tests_request',
|
|
41
|
+
attributes: {
|
|
42
|
+
repository_url: repositoryUrl
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
request(data, options, (err, res) => {
|
|
48
|
+
if (err) {
|
|
49
|
+
done(err)
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
const { data: { attributes: { modules: quarantinedTests } } } = JSON.parse(res)
|
|
53
|
+
|
|
54
|
+
done(null, quarantinedTests)
|
|
55
|
+
} catch (err) {
|
|
56
|
+
done(err)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { getQuarantinedTests }
|
|
@@ -94,7 +94,8 @@ function getLibraryConfiguration ({
|
|
|
94
94
|
early_flake_detection: earlyFlakeDetectionConfig,
|
|
95
95
|
flaky_test_retries_enabled: isFlakyTestRetriesEnabled,
|
|
96
96
|
di_enabled: isDiEnabled,
|
|
97
|
-
known_tests_enabled: isKnownTestsEnabled
|
|
97
|
+
known_tests_enabled: isKnownTestsEnabled,
|
|
98
|
+
test_management: testManagementConfig
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
} = JSON.parse(res)
|
|
@@ -111,7 +112,9 @@ function getLibraryConfiguration ({
|
|
|
111
112
|
earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
|
|
112
113
|
isFlakyTestRetriesEnabled,
|
|
113
114
|
isDiEnabled: isDiEnabled && isFlakyTestRetriesEnabled,
|
|
114
|
-
isKnownTestsEnabled
|
|
115
|
+
isKnownTestsEnabled,
|
|
116
|
+
// TODO: should it be test management?
|
|
117
|
+
isQuarantinedTestsEnabled: (testManagementConfig?.enabled ?? false)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
log.debug(() => `Remote settings: ${JSON.stringify(settings)}`)
|
|
@@ -497,6 +497,7 @@ class Config {
|
|
|
497
497
|
this._setValue(defaults, 'iast.redactionNamePattern', null)
|
|
498
498
|
this._setValue(defaults, 'iast.redactionValuePattern', null)
|
|
499
499
|
this._setValue(defaults, 'iast.requestSampling', 30)
|
|
500
|
+
this._setValue(defaults, 'iast.securityControlsConfiguration', null)
|
|
500
501
|
this._setValue(defaults, 'iast.telemetryVerbosity', 'INFORMATION')
|
|
501
502
|
this._setValue(defaults, 'iast.stackTrace.enabled', true)
|
|
502
503
|
this._setValue(defaults, 'injectionEnabled', [])
|
|
@@ -519,6 +520,8 @@ class Config {
|
|
|
519
520
|
this._setValue(defaults, 'legacyBaggageEnabled', true)
|
|
520
521
|
this._setValue(defaults, 'isTestDynamicInstrumentationEnabled', false)
|
|
521
522
|
this._setValue(defaults, 'isServiceUserProvided', false)
|
|
523
|
+
this._setValue(defaults, 'testManagementAttemptToFixRetries', 20)
|
|
524
|
+
this._setValue(defaults, 'isTestManagementEnabled', false)
|
|
522
525
|
this._setValue(defaults, 'logInjection', false)
|
|
523
526
|
this._setValue(defaults, 'lookup', undefined)
|
|
524
527
|
this._setValue(defaults, 'inferredProxyServicesEnabled', false)
|
|
@@ -625,6 +628,7 @@ class Config {
|
|
|
625
628
|
DD_IAST_REDACTION_NAME_PATTERN,
|
|
626
629
|
DD_IAST_REDACTION_VALUE_PATTERN,
|
|
627
630
|
DD_IAST_REQUEST_SAMPLING,
|
|
631
|
+
DD_IAST_SECURITY_CONTROLS_CONFIGURATION,
|
|
628
632
|
DD_IAST_TELEMETRY_VERBOSITY,
|
|
629
633
|
DD_IAST_STACK_TRACE_ENABLED,
|
|
630
634
|
DD_INJECTION_ENABLED,
|
|
@@ -793,6 +797,7 @@ class Config {
|
|
|
793
797
|
this._setValue(env, 'iast.requestSampling', iastRequestSampling)
|
|
794
798
|
}
|
|
795
799
|
this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
|
|
800
|
+
this._setString(env, 'iast.securityControlsConfiguration', DD_IAST_SECURITY_CONTROLS_CONFIGURATION)
|
|
796
801
|
this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
|
|
797
802
|
this._setBoolean(env, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED)
|
|
798
803
|
this._setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED)
|
|
@@ -985,14 +990,15 @@ class Config {
|
|
|
985
990
|
this._setValue(opts, 'iast.requestSampling', iastRequestSampling)
|
|
986
991
|
this._optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling
|
|
987
992
|
}
|
|
988
|
-
this.
|
|
993
|
+
this._setValue(opts, 'iast.securityControlsConfiguration', options.iast?.securityControlsConfiguration)
|
|
989
994
|
this._setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled)
|
|
995
|
+
this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
|
|
990
996
|
this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
|
|
991
997
|
this._setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled)
|
|
992
998
|
this._setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled)
|
|
993
999
|
this._setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp)
|
|
994
1000
|
this._setBoolean(opts, 'logInjection', options.logInjection)
|
|
995
|
-
this.
|
|
1001
|
+
this._setValue(opts, 'lookup', options.lookup)
|
|
996
1002
|
this._setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled)
|
|
997
1003
|
this._setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled)
|
|
998
1004
|
this._setValue(opts, 'peerServiceMapping', options.peerServiceMapping)
|
|
@@ -1138,7 +1144,9 @@ class Config {
|
|
|
1138
1144
|
DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
|
|
1139
1145
|
DD_TEST_SESSION_NAME,
|
|
1140
1146
|
DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
|
|
1141
|
-
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED
|
|
1147
|
+
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED,
|
|
1148
|
+
DD_TEST_MANAGEMENT_ENABLED,
|
|
1149
|
+
DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES
|
|
1142
1150
|
} = process.env
|
|
1143
1151
|
|
|
1144
1152
|
if (DD_CIVISIBILITY_AGENTLESS_URL) {
|
|
@@ -1158,6 +1166,11 @@ class Config {
|
|
|
1158
1166
|
this._setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', isTrue(DD_AGENTLESS_LOG_SUBMISSION_ENABLED))
|
|
1159
1167
|
this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', isTrue(DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED))
|
|
1160
1168
|
this._setBoolean(calc, 'isServiceUserProvided', !!this._env.service)
|
|
1169
|
+
this._setBoolean(calc, 'isTestManagementEnabled', !isFalse(DD_TEST_MANAGEMENT_ENABLED))
|
|
1170
|
+
this._setValue(calc,
|
|
1171
|
+
'testManagementAttemptToFixRetries',
|
|
1172
|
+
coalesce(maybeInt(DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES), 20)
|
|
1173
|
+
)
|
|
1161
1174
|
}
|
|
1162
1175
|
this._setString(calc, 'dogstatsd.hostname', this._getHostname())
|
|
1163
1176
|
this._setBoolean(calc, 'isGitUploadEnabled',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { getGeneratedPosition } = require('./source-maps')
|
|
3
4
|
const session = require('./session')
|
|
4
5
|
const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
|
|
5
6
|
const { findScriptFromPartialPath, probes, breakpoints } = require('./state')
|
|
@@ -16,10 +17,11 @@ async function addBreakpoint (probe) {
|
|
|
16
17
|
if (!sessionStarted) await start()
|
|
17
18
|
|
|
18
19
|
const file = probe.where.sourceFile
|
|
19
|
-
|
|
20
|
+
let lineNumber = Number(probe.where.lines[0]) // Tracer doesn't support multiple-line breakpoints
|
|
21
|
+
let columnNumber = 0 // Probes do not contain/support column information
|
|
20
22
|
|
|
21
23
|
// Optimize for sending data to /debugger/v1/input endpoint
|
|
22
|
-
probe.location = { file, lines: [String(
|
|
24
|
+
probe.location = { file, lines: [String(lineNumber)] }
|
|
23
25
|
delete probe.where
|
|
24
26
|
|
|
25
27
|
// Optimize for fast calculations when probe is hit
|
|
@@ -34,17 +36,22 @@ async function addBreakpoint (probe) {
|
|
|
34
36
|
// not continue untill all scripts have been parsed?
|
|
35
37
|
const script = findScriptFromPartialPath(file)
|
|
36
38
|
if (!script) throw new Error(`No loaded script found for ${file} (probe: ${probe.id}, version: ${probe.version})`)
|
|
37
|
-
const
|
|
39
|
+
const { url, scriptId, sourceMapURL, source } = script
|
|
40
|
+
|
|
41
|
+
if (sourceMapURL) {
|
|
42
|
+
({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, lineNumber, sourceMapURL))
|
|
43
|
+
}
|
|
38
44
|
|
|
39
45
|
log.debug(
|
|
40
|
-
'[debugger:devtools_client] Adding breakpoint at %s:%d (probe: %s, version: %d)',
|
|
41
|
-
|
|
46
|
+
'[debugger:devtools_client] Adding breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
47
|
+
url, lineNumber, columnNumber, probe.id, probe.version
|
|
42
48
|
)
|
|
43
49
|
|
|
44
50
|
const { breakpointId } = await session.post('Debugger.setBreakpoint', {
|
|
45
51
|
location: {
|
|
46
52
|
scriptId,
|
|
47
|
-
lineNumber:
|
|
53
|
+
lineNumber: lineNumber - 1, // Beware! lineNumber is zero-indexed
|
|
54
|
+
columnNumber
|
|
48
55
|
}
|
|
49
56
|
})
|
|
50
57
|
|
|
@@ -66,7 +73,7 @@ async function removeBreakpoint ({ id }) {
|
|
|
66
73
|
probes.delete(id)
|
|
67
74
|
breakpoints.delete(breakpointId)
|
|
68
75
|
|
|
69
|
-
if (breakpoints.size === 0)
|
|
76
|
+
if (breakpoints.size === 0) return stop() // return instead of await to reduce number of promises created
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
async function start () {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { join, dirname } = require('path')
|
|
4
|
+
const { readFileSync } = require('fs')
|
|
5
|
+
const { readFile } = require('fs/promises')
|
|
6
|
+
const { SourceMapConsumer } = require('source-map')
|
|
7
|
+
|
|
8
|
+
const cache = new Map()
|
|
9
|
+
let cacheTimer = null
|
|
10
|
+
|
|
11
|
+
const self = module.exports = {
|
|
12
|
+
async loadSourceMap (dir, url) {
|
|
13
|
+
if (url.startsWith('data:')) return loadInlineSourceMap(url)
|
|
14
|
+
const path = join(dir, url)
|
|
15
|
+
if (cache.has(path)) return cache.get(path)
|
|
16
|
+
return cacheIt(path, JSON.parse(await readFile(path, 'utf8')))
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
loadSourceMapSync (dir, url) {
|
|
20
|
+
if (url.startsWith('data:')) return loadInlineSourceMap(url)
|
|
21
|
+
const path = join(dir, url)
|
|
22
|
+
if (cache.has(path)) return cache.get(path)
|
|
23
|
+
return cacheIt(path, JSON.parse(readFileSync(path, 'utf8')))
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async getGeneratedPosition (url, source, line, sourceMapURL) {
|
|
27
|
+
const dir = dirname(new URL(url).pathname)
|
|
28
|
+
return await SourceMapConsumer.with(
|
|
29
|
+
await self.loadSourceMap(dir, sourceMapURL),
|
|
30
|
+
null,
|
|
31
|
+
(consumer) => consumer.generatedPositionFor({ source, line, column: 0 })
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cacheIt (key, value) {
|
|
37
|
+
clearTimeout(cacheTimer)
|
|
38
|
+
cacheTimer = setTimeout(function () {
|
|
39
|
+
// Optimize for app boot, where a lot of reads might happen
|
|
40
|
+
// Clear cache a few seconds after it was last used
|
|
41
|
+
cache.clear()
|
|
42
|
+
}, 10_000).unref()
|
|
43
|
+
cache.set(key, value)
|
|
44
|
+
return value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadInlineSourceMap (data) {
|
|
48
|
+
data = data.slice(data.indexOf('base64,') + 7)
|
|
49
|
+
return JSON.parse(Buffer.from(data, 'base64').toString('utf8'))
|
|
50
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { join, dirname } = require('path')
|
|
4
|
+
const { loadSourceMapSync } = require('./source-maps')
|
|
3
5
|
const session = require('./session')
|
|
6
|
+
const log = require('../../log')
|
|
4
7
|
|
|
5
8
|
const WINDOWS_DRIVE_LETTER_REGEX = /[a-zA-Z]/
|
|
6
9
|
|
|
7
|
-
const
|
|
10
|
+
const loadedScripts = []
|
|
8
11
|
const scriptUrls = new Map()
|
|
9
12
|
|
|
10
13
|
module.exports = {
|
|
@@ -15,18 +18,17 @@ module.exports = {
|
|
|
15
18
|
* Find the script to inspect based on a partial or absolute path. Handles both Windows and POSIX paths.
|
|
16
19
|
*
|
|
17
20
|
* @param {string} path - Partial or absolute path to match against loaded scripts
|
|
18
|
-
* @returns {
|
|
19
|
-
* or null if no match
|
|
21
|
+
* @returns {Object | null} - Object containing `url`, `scriptId`, `sourceMapURL`, and `source` - or null if no match
|
|
20
22
|
*/
|
|
21
23
|
findScriptFromPartialPath (path) {
|
|
22
24
|
if (!path) return null // This shouldn't happen, but better safe than sorry
|
|
23
25
|
|
|
24
26
|
path = path.toLowerCase()
|
|
25
27
|
|
|
26
|
-
const bestMatch =
|
|
28
|
+
const bestMatch = { url: null, scriptId: null, sourceMapURL: null, source: null }
|
|
27
29
|
let maxMatchLength = -1
|
|
28
30
|
|
|
29
|
-
for (const
|
|
31
|
+
for (const { url, sourceUrl, scriptId, sourceMapURL, source } of loadedScripts) {
|
|
30
32
|
let i = url.length - 1
|
|
31
33
|
let j = path.length - 1
|
|
32
34
|
let matchLength = 0
|
|
@@ -75,12 +77,13 @@ module.exports = {
|
|
|
75
77
|
// If we found a valid match and it's better than our previous best
|
|
76
78
|
if (atBoundary && (
|
|
77
79
|
lastBoundaryPos > maxMatchLength ||
|
|
78
|
-
(lastBoundaryPos === maxMatchLength && url.length < bestMatch
|
|
80
|
+
(lastBoundaryPos === maxMatchLength && url.length < bestMatch.url.length) // Prefer shorter paths
|
|
79
81
|
)) {
|
|
80
82
|
maxMatchLength = lastBoundaryPos
|
|
81
|
-
bestMatch
|
|
82
|
-
bestMatch
|
|
83
|
-
bestMatch
|
|
83
|
+
bestMatch.url = sourceUrl || url
|
|
84
|
+
bestMatch.scriptId = scriptId
|
|
85
|
+
bestMatch.sourceMapURL = sourceMapURL
|
|
86
|
+
bestMatch.source = source
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -112,6 +115,31 @@ module.exports = {
|
|
|
112
115
|
session.on('Debugger.scriptParsed', ({ params }) => {
|
|
113
116
|
scriptUrls.set(params.scriptId, params.url)
|
|
114
117
|
if (params.url.startsWith('file:')) {
|
|
115
|
-
|
|
118
|
+
if (params.sourceMapURL) {
|
|
119
|
+
const dir = dirname(new URL(params.url).pathname)
|
|
120
|
+
let sources
|
|
121
|
+
try {
|
|
122
|
+
sources = loadSourceMapSync(dir, params.sourceMapURL).sources
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (typeof params.sourceMapURL === 'string' && params.sourceMapURL.startsWith('data:')) {
|
|
125
|
+
log.error('[debugger:devtools_client] could not load inline source map for "%s"', params.url, err)
|
|
126
|
+
} else {
|
|
127
|
+
log.error('[debugger:devtools_client] could not load source map "%s" from "%s" for "%s"',
|
|
128
|
+
params.sourceMapURL, dir, params.url, err)
|
|
129
|
+
}
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
for (const source of sources) {
|
|
133
|
+
// TODO: Take source map `sourceRoot` into account?
|
|
134
|
+
loadedScripts.push({
|
|
135
|
+
...params,
|
|
136
|
+
sourceUrl: params.url,
|
|
137
|
+
url: new URL(join(dir, source), 'file:').href,
|
|
138
|
+
source
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
loadedScripts.push(params)
|
|
143
|
+
}
|
|
116
144
|
}
|
|
117
145
|
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const satisfies = require('semifies')
|
|
4
4
|
const logger = require('./log')
|
|
5
5
|
const { addHook } = require('import-in-the-middle')
|
|
6
6
|
const dc = require('dc-polyfill')
|
|
7
7
|
|
|
8
|
-
if (
|
|
8
|
+
if (satisfies(process.versions.node, '>=14.13.1')) {
|
|
9
9
|
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
10
10
|
addHook((name, namespace) => {
|
|
11
11
|
if (moduleLoadStartChannel.hasSubscribers) {
|
|
@@ -100,7 +100,12 @@ class LLMObsTagger {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
tagMetadata (span, metadata) {
|
|
103
|
-
|
|
103
|
+
const existingMetadata = registry.get(span)?.[METADATA]
|
|
104
|
+
if (existingMetadata) {
|
|
105
|
+
Object.assign(existingMetadata, metadata)
|
|
106
|
+
} else {
|
|
107
|
+
this._setTag(span, METADATA, metadata)
|
|
108
|
+
}
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
tagMetrics (span, metrics) {
|
|
@@ -128,7 +133,12 @@ class LLMObsTagger {
|
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
const existingMetrics = registry.get(span)?.[METRICS]
|
|
137
|
+
if (existingMetrics) {
|
|
138
|
+
Object.assign(existingMetrics, filterdMetrics)
|
|
139
|
+
} else {
|
|
140
|
+
this._setTag(span, METRICS, filterdMetrics)
|
|
141
|
+
}
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
tagSpanTags (span, tags) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const { performance } = require('perf_hooks')
|
|
5
5
|
const now = performance.now.bind(performance)
|
|
6
6
|
const dateNow = Date.now
|
|
7
|
-
const
|
|
7
|
+
const satisfies = require('semifies')
|
|
8
8
|
const SpanContext = require('./span_context')
|
|
9
9
|
const id = require('../id')
|
|
10
10
|
const tagger = require('../tagger')
|
|
@@ -365,7 +365,7 @@ class DatadogSpan {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
function createRegistry (type) {
|
|
368
|
-
if (!
|
|
368
|
+
if (!satisfies(process.version, '>=14.6')) return
|
|
369
369
|
|
|
370
370
|
return new global.FinalizationRegistry(name => {
|
|
371
371
|
runtimeMetrics.decrement(`runtime.node.spans.${type}`)
|
|
@@ -50,7 +50,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
50
50
|
|
|
51
51
|
this.addSub(`ci:${this.constructor.id}:library-configuration`, ({ onDone }) => {
|
|
52
52
|
if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) {
|
|
53
|
-
return onDone({ err: new Error('
|
|
53
|
+
return onDone({ err: new Error('Test optimization was not initialized correctly') })
|
|
54
54
|
}
|
|
55
55
|
this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
|
|
56
56
|
if (err) {
|
|
@@ -64,7 +64,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
64
64
|
|
|
65
65
|
this.addSub(`ci:${this.constructor.id}:test-suite:skippable`, ({ onDone }) => {
|
|
66
66
|
if (!this.tracer._exporter?.getSkippableSuites) {
|
|
67
|
-
return onDone({ err: new Error('
|
|
67
|
+
return onDone({ err: new Error('Test optimization was not initialized correctly') })
|
|
68
68
|
}
|
|
69
69
|
this.tracer._exporter.getSkippableSuites(this.testConfiguration, (err, skippableSuites, itrCorrelationId) => {
|
|
70
70
|
if (err) {
|
|
@@ -153,7 +153,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
153
153
|
|
|
154
154
|
this.addSub(`ci:${this.constructor.id}:known-tests`, ({ onDone }) => {
|
|
155
155
|
if (!this.tracer._exporter?.getKnownTests) {
|
|
156
|
-
return onDone({ err: new Error('
|
|
156
|
+
return onDone({ err: new Error('Test optimization was not initialized correctly') })
|
|
157
157
|
}
|
|
158
158
|
this.tracer._exporter.getKnownTests(this.testConfiguration, (err, knownTests) => {
|
|
159
159
|
if (err) {
|
|
@@ -164,6 +164,19 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
164
164
|
onDone({ err, knownTests })
|
|
165
165
|
})
|
|
166
166
|
})
|
|
167
|
+
|
|
168
|
+
this.addSub(`ci:${this.constructor.id}:quarantined-tests`, ({ onDone }) => {
|
|
169
|
+
if (!this.tracer._exporter?.getQuarantinedTests) {
|
|
170
|
+
return onDone({ err: new Error('Test optimization was not initialized correctly') })
|
|
171
|
+
}
|
|
172
|
+
this.tracer._exporter.getQuarantinedTests(this.testConfiguration, (err, quarantinedTests) => {
|
|
173
|
+
if (err) {
|
|
174
|
+
log.error('Quarantined tests could not be fetched. %s', err.message)
|
|
175
|
+
this.libraryConfig.isQuarantinedTestsEnabled = false
|
|
176
|
+
}
|
|
177
|
+
onDone({ err, quarantinedTests })
|
|
178
|
+
})
|
|
179
|
+
})
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
get telemetry () {
|
|
@@ -63,25 +63,35 @@ class DatabasePlugin extends StoragePlugin {
|
|
|
63
63
|
return tracerService
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
createDbmComment (span, serviceName, isPreparedStatement = false) {
|
|
67
67
|
const mode = this.config.dbmPropagationMode
|
|
68
68
|
const dbmService = this.getDbmServiceName(span, serviceName)
|
|
69
69
|
|
|
70
70
|
if (mode === 'disabled') {
|
|
71
|
-
return
|
|
71
|
+
return null
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const servicePropagation = this.createDBMPropagationCommentService(dbmService, span)
|
|
75
75
|
|
|
76
76
|
if (isPreparedStatement || mode === 'service') {
|
|
77
|
-
return
|
|
77
|
+
return servicePropagation
|
|
78
78
|
} else if (mode === 'full') {
|
|
79
79
|
span.setTag('_dd.dbm_trace_injected', 'true')
|
|
80
80
|
const traceparent = span._spanContext.toTraceparent()
|
|
81
|
-
return
|
|
81
|
+
return `${servicePropagation},traceparent='${traceparent}'`
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
injectDbmQuery (span, query, serviceName, isPreparedStatement = false) {
|
|
86
|
+
const dbmTraceComment = this.createDbmComment(span, serviceName, isPreparedStatement)
|
|
87
|
+
|
|
88
|
+
if (!dbmTraceComment) {
|
|
89
|
+
return query
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `/*${dbmTraceComment}*/ ${query}`
|
|
93
|
+
}
|
|
94
|
+
|
|
85
95
|
maybeTruncate (query) {
|
|
86
96
|
const maxLength = typeof this.config.truncate === 'number'
|
|
87
97
|
? this.config.truncate
|
|
@@ -2,7 +2,6 @@ const log = require('../../log')
|
|
|
2
2
|
const tags = require('../../../../../ext/tags')
|
|
3
3
|
|
|
4
4
|
const RESOURCE_NAME = tags.RESOURCE_NAME
|
|
5
|
-
const SPAN_KIND = tags.SPAN_KIND
|
|
6
5
|
const SPAN_TYPE = tags.SPAN_TYPE
|
|
7
6
|
const HTTP_URL = tags.HTTP_URL
|
|
8
7
|
const HTTP_METHOD = tags.HTTP_METHOD
|
|
@@ -49,7 +48,6 @@ function createInferredProxySpan (headers, childOf, tracer, context) {
|
|
|
49
48
|
tags: {
|
|
50
49
|
service: proxyContext.domainName || tracer._config.service,
|
|
51
50
|
component: proxySpanInfo.component,
|
|
52
|
-
[SPAN_KIND]: 'internal',
|
|
53
51
|
[SPAN_TYPE]: 'web',
|
|
54
52
|
[HTTP_METHOD]: proxyContext.method,
|
|
55
53
|
[HTTP_URL]: proxyContext.domainName + proxyContext.path,
|
|
@@ -71,7 +69,7 @@ function createInferredProxySpan (headers, childOf, tracer, context) {
|
|
|
71
69
|
|
|
72
70
|
function setInferredProxySpanTags (span, proxyContext) {
|
|
73
71
|
span.setTag(RESOURCE_NAME, `${proxyContext.method} ${proxyContext.path}`)
|
|
74
|
-
span.setTag('_dd.inferred_span',
|
|
72
|
+
span.setTag('_dd.inferred_span', 1)
|
|
75
73
|
return span
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -52,8 +52,6 @@ const TEST_MODULE_ID = 'test_module_id'
|
|
|
52
52
|
const TEST_SUITE_ID = 'test_suite_id'
|
|
53
53
|
const TEST_TOOLCHAIN = 'test.toolchain'
|
|
54
54
|
const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
|
|
55
|
-
// Browser used in browser test. Namespaced by test.configuration because it affects the fingerprint
|
|
56
|
-
const TEST_CONFIGURATION_BROWSER_NAME = 'test.configuration.browser_name'
|
|
57
55
|
// Early flake detection
|
|
58
56
|
const TEST_IS_NEW = 'test.is_new'
|
|
59
57
|
const TEST_IS_RETRY = 'test.is_retry'
|
|
@@ -117,6 +115,9 @@ const DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX = 'snapshot_id'
|
|
|
117
115
|
const DI_DEBUG_ERROR_FILE_SUFFIX = 'file'
|
|
118
116
|
const DI_DEBUG_ERROR_LINE_SUFFIX = 'line'
|
|
119
117
|
|
|
118
|
+
const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
|
|
119
|
+
const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
|
|
120
|
+
|
|
120
121
|
module.exports = {
|
|
121
122
|
TEST_CODE_OWNERS,
|
|
122
123
|
TEST_SESSION_NAME,
|
|
@@ -143,7 +144,6 @@ module.exports = {
|
|
|
143
144
|
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
144
145
|
TEST_SOURCE_START,
|
|
145
146
|
TEST_SKIPPED_BY_ITR,
|
|
146
|
-
TEST_CONFIGURATION_BROWSER_NAME,
|
|
147
147
|
TEST_IS_NEW,
|
|
148
148
|
TEST_IS_RETRY,
|
|
149
149
|
TEST_EARLY_FLAKE_ENABLED,
|
|
@@ -202,7 +202,9 @@ module.exports = {
|
|
|
202
202
|
DI_DEBUG_ERROR_FILE_SUFFIX,
|
|
203
203
|
DI_DEBUG_ERROR_LINE_SUFFIX,
|
|
204
204
|
getFormattedError,
|
|
205
|
-
DD_TEST_IS_USER_PROVIDED_SERVICE
|
|
205
|
+
DD_TEST_IS_USER_PROVIDED_SERVICE,
|
|
206
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
207
|
+
TEST_MANAGEMENT_ENABLED
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -201,7 +201,11 @@ class Tracer extends NoopProxy {
|
|
|
201
201
|
try {
|
|
202
202
|
return require('./profiler').start(config)
|
|
203
203
|
} catch (e) {
|
|
204
|
-
log.error(
|
|
204
|
+
log.error(
|
|
205
|
+
'Error starting profiler. For troubleshooting tips, see ' +
|
|
206
|
+
'<https://dtdg.co/nodejs-profiler-troubleshooting>',
|
|
207
|
+
e
|
|
208
|
+
)
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
|
|
@@ -94,10 +94,11 @@ function Hook (modules, options, onrequire) {
|
|
|
94
94
|
if (moduleLoadStartChannel.hasSubscribers) {
|
|
95
95
|
moduleLoadStartChannel.publish(payload)
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
let exports = origRequire.apply(this, arguments)
|
|
98
98
|
payload.module = exports
|
|
99
99
|
if (moduleLoadEndChannel.hasSubscribers) {
|
|
100
100
|
moduleLoadEndChannel.publish(payload)
|
|
101
|
+
exports = payload.module
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// The module has already been loaded,
|