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.
Files changed (65) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +5 -0
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +14 -13
  5. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  6. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  7. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  8. package/packages/datadog-instrumentations/src/jest.js +103 -5
  9. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  12. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  13. package/packages/datadog-instrumentations/src/openai.js +8 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  15. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  16. package/packages/datadog-plugin-cucumber/src/index.js +20 -3
  17. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
  18. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  19. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  20. package/packages/datadog-plugin-jest/src/index.js +12 -2
  21. package/packages/datadog-plugin-mocha/src/index.js +22 -3
  22. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  23. package/packages/datadog-plugin-openai/src/tracing.js +1 -2
  24. package/packages/datadog-plugin-playwright/src/index.js +31 -5
  25. package/packages/datadog-plugin-vitest/src/index.js +25 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
  27. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  28. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
  29. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
  30. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  32. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
  33. package/packages/dd-trace/src/appsec/iast/index.js +2 -0
  34. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  35. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  39. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  40. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  41. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  42. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  43. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
  44. package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
  45. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
  46. package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
  47. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  49. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  51. package/packages/dd-trace/src/config.js +16 -3
  52. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  53. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  54. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  55. package/packages/dd-trace/src/iitm.js +2 -2
  56. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  57. package/packages/dd-trace/src/opentracing/span.js +2 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
  59. package/packages/dd-trace/src/plugins/database.js +14 -4
  60. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  61. package/packages/dd-trace/src/plugins/util/test.js +6 -4
  62. package/packages/dd-trace/src/proxy.js +5 -1
  63. package/packages/dd-trace/src/ritm.js +2 -1
  64. package/packages/dd-trace/src/spanleak.js +0 -1
  65. 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._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
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._setString(opts, 'lookup', options.lookup)
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
- const line = Number(probe.where.lines[0]) // Tracer doesn't support multiple-line breakpoints
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(line)] }
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 [path, scriptId] = script
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
- path, line, probe.id, probe.version
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: line - 1 // Beware! lineNumber is zero-indexed
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) await stop()
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 scriptIds = []
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 {[string, string, string | undefined] | null} - Array containing [url, scriptId, sourceMapURL]
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 = new Array(3)
28
+ const bestMatch = { url: null, scriptId: null, sourceMapURL: null, source: null }
27
29
  let maxMatchLength = -1
28
30
 
29
- for (const [url, scriptId, sourceMapURL] of scriptIds) {
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[0].length) // Prefer shorter paths
80
+ (lastBoundaryPos === maxMatchLength && url.length < bestMatch.url.length) // Prefer shorter paths
79
81
  )) {
80
82
  maxMatchLength = lastBoundaryPos
81
- bestMatch[0] = url
82
- bestMatch[1] = scriptId
83
- bestMatch[2] = sourceMapURL
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
- scriptIds.push([params.url, params.scriptId, params.sourceMapURL])
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 semver = require('semver')
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 (semver.satisfies(process.versions.node, '>=14.13.1')) {
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
- this._setTag(span, METADATA, metadata)
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
- this._setTag(span, METRICS, filterdMetrics)
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 semver = require('semver')
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 (!semver.satisfies(process.version, '>=14.6')) return
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('CI Visibility was not initialized correctly') })
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('CI Visibility was not initialized correctly') })
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('CI Visibility was not initialized correctly') })
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
- injectDbmQuery (span, query, serviceName, isPreparedStatement = false) {
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 query
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 `/*${servicePropagation}*/ ${query}`
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 `/*${servicePropagation},traceparent='${traceparent}'*/ ${query}`
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', '1')
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('Error starting profiler', e)
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
- const exports = origRequire.apply(this, arguments)
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,
@@ -83,7 +83,6 @@ module.exports.addSpan = function (span) {
83
83
 
84
84
  const now = Date.now()
85
85
  const expiration = now + LIFETIME
86
- // eslint-disable-next-line no-undef
87
86
  const wrapped = new WeakRef(span)
88
87
  spans.add(wrapped, expiration)
89
88
  // registry.register(span, span._name)
@@ -1,9 +0,0 @@
1
- {
2
- "name": "memwatch",
3
- "version": "1.0.0",
4
- "license": "BSD-3-Clause",
5
- "private": true,
6
- "dependencies": {
7
- "@airbnb/node-memwatch": "^1.0.2"
8
- }
9
- }