dd-trace 5.35.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 (125) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +8 -7
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +15 -14
  5. package/packages/datadog-core/index.js +1 -1
  6. package/packages/datadog-core/src/storage.js +76 -31
  7. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  8. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  10. package/packages/datadog-instrumentations/src/jest.js +105 -11
  11. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  14. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  15. package/packages/datadog-instrumentations/src/openai.js +8 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  17. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  18. package/packages/datadog-plugin-aerospike/src/index.js +1 -1
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
  20. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  21. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
  22. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
  25. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  26. package/packages/datadog-plugin-couchbase/src/index.js +2 -2
  27. package/packages/datadog-plugin-cucumber/src/index.js +31 -14
  28. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
  29. package/packages/datadog-plugin-cypress/src/support.js +36 -29
  30. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  31. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  32. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  33. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  34. package/packages/datadog-plugin-hapi/src/index.js +1 -1
  35. package/packages/datadog-plugin-http/src/client.js +1 -1
  36. package/packages/datadog-plugin-http/src/server.js +1 -1
  37. package/packages/datadog-plugin-http2/src/client.js +3 -3
  38. package/packages/datadog-plugin-http2/src/server.js +1 -1
  39. package/packages/datadog-plugin-jest/src/index.js +17 -12
  40. package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
  41. package/packages/datadog-plugin-mariadb/src/index.js +3 -3
  42. package/packages/datadog-plugin-mocha/src/index.js +35 -16
  43. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  44. package/packages/datadog-plugin-next/src/index.js +4 -4
  45. package/packages/datadog-plugin-openai/src/tracing.js +2 -3
  46. package/packages/datadog-plugin-playwright/src/index.js +35 -9
  47. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  48. package/packages/datadog-plugin-router/src/index.js +2 -2
  49. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  50. package/packages/datadog-plugin-vitest/src/index.js +36 -12
  51. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  52. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
  55. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
  57. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  59. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
  60. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
  62. package/packages/dd-trace/src/appsec/iast/index.js +4 -2
  63. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  64. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  65. package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  67. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
  68. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
  69. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
  70. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
  71. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
  72. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  75. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  76. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  78. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  79. package/packages/dd-trace/src/appsec/index.js +4 -4
  80. package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
  81. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
  82. package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
  83. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
  84. package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
  85. package/packages/dd-trace/src/appsec/reporter.js +3 -3
  86. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  87. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
  90. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  91. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  92. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  93. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
  94. package/packages/dd-trace/src/config.js +18 -3
  95. package/packages/dd-trace/src/data_streams_context.js +2 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  97. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  99. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  100. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  101. package/packages/dd-trace/src/iitm.js +2 -2
  102. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
  103. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  104. package/packages/dd-trace/src/log/writer.js +3 -3
  105. package/packages/dd-trace/src/noop/span.js +1 -1
  106. package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
  107. package/packages/dd-trace/src/opentracing/span.js +3 -3
  108. package/packages/dd-trace/src/plugin_manager.js +3 -1
  109. package/packages/dd-trace/src/plugins/apollo.js +1 -1
  110. package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
  111. package/packages/dd-trace/src/plugins/database.js +14 -4
  112. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  113. package/packages/dd-trace/src/plugins/plugin.js +8 -8
  114. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  115. package/packages/dd-trace/src/plugins/util/git.js +3 -3
  116. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  117. package/packages/dd-trace/src/plugins/util/test.js +10 -4
  118. package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  120. package/packages/dd-trace/src/proxy.js +5 -1
  121. package/packages/dd-trace/src/ritm.js +2 -1
  122. package/packages/dd-trace/src/scope.js +5 -5
  123. package/packages/dd-trace/src/spanleak.js +0 -1
  124. package/packages/dd-trace/src/tracer.js +0 -14
  125. 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)}`)
@@ -17,13 +17,13 @@ class TestApiManualPlugin extends CiPlugin {
17
17
  this.sourceRoot = process.cwd()
18
18
 
19
19
  this.unconfiguredAddSub('dd-trace:ci:manual:test:start', ({ testName, testSuite }) => {
20
- const store = storage.getStore()
20
+ const store = storage('legacy').getStore()
21
21
  const testSuiteRelative = getTestSuitePath(testSuite, this.sourceRoot)
22
22
  const testSpan = this.startTestSpan(testName, testSuiteRelative)
23
23
  this.enter(testSpan, store)
24
24
  })
25
25
  this.unconfiguredAddSub('dd-trace:ci:manual:test:finish', ({ status, error }) => {
26
- const store = storage.getStore()
26
+ const store = storage('legacy').getStore()
27
27
  const testSpan = store && store.span
28
28
  if (testSpan) {
29
29
  testSpan.setTag(TEST_STATUS, status)
@@ -35,7 +35,7 @@ class TestApiManualPlugin extends CiPlugin {
35
35
  }
36
36
  })
37
37
  this.unconfiguredAddSub('dd-trace:ci:manual:test:addTags', (tags) => {
38
- const store = storage.getStore()
38
+ const store = storage('legacy').getStore()
39
39
  const testSpan = store && store.span
40
40
  if (testSpan) {
41
41
  testSpan.addTags(tags)
@@ -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', [])
@@ -518,6 +519,9 @@ class Config {
518
519
  this._setValue(defaults, 'ciVisAgentlessLogSubmissionEnabled', false)
519
520
  this._setValue(defaults, 'legacyBaggageEnabled', true)
520
521
  this._setValue(defaults, 'isTestDynamicInstrumentationEnabled', false)
522
+ this._setValue(defaults, 'isServiceUserProvided', false)
523
+ this._setValue(defaults, 'testManagementAttemptToFixRetries', 20)
524
+ this._setValue(defaults, 'isTestManagementEnabled', false)
521
525
  this._setValue(defaults, 'logInjection', false)
522
526
  this._setValue(defaults, 'lookup', undefined)
523
527
  this._setValue(defaults, 'inferredProxyServicesEnabled', false)
@@ -624,6 +628,7 @@ class Config {
624
628
  DD_IAST_REDACTION_NAME_PATTERN,
625
629
  DD_IAST_REDACTION_VALUE_PATTERN,
626
630
  DD_IAST_REQUEST_SAMPLING,
631
+ DD_IAST_SECURITY_CONTROLS_CONFIGURATION,
627
632
  DD_IAST_TELEMETRY_VERBOSITY,
628
633
  DD_IAST_STACK_TRACE_ENABLED,
629
634
  DD_INJECTION_ENABLED,
@@ -792,6 +797,7 @@ class Config {
792
797
  this._setValue(env, 'iast.requestSampling', iastRequestSampling)
793
798
  }
794
799
  this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
800
+ this._setString(env, 'iast.securityControlsConfiguration', DD_IAST_SECURITY_CONTROLS_CONFIGURATION)
795
801
  this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
796
802
  this._setBoolean(env, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED)
797
803
  this._setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED)
@@ -984,14 +990,15 @@ class Config {
984
990
  this._setValue(opts, 'iast.requestSampling', iastRequestSampling)
985
991
  this._optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling
986
992
  }
987
- this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
993
+ this._setValue(opts, 'iast.securityControlsConfiguration', options.iast?.securityControlsConfiguration)
988
994
  this._setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled)
995
+ this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
989
996
  this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
990
997
  this._setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled)
991
998
  this._setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled)
992
999
  this._setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp)
993
1000
  this._setBoolean(opts, 'logInjection', options.logInjection)
994
- this._setString(opts, 'lookup', options.lookup)
1001
+ this._setValue(opts, 'lookup', options.lookup)
995
1002
  this._setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled)
996
1003
  this._setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled)
997
1004
  this._setValue(opts, 'peerServiceMapping', options.peerServiceMapping)
@@ -1137,7 +1144,9 @@ class Config {
1137
1144
  DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
1138
1145
  DD_TEST_SESSION_NAME,
1139
1146
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
1140
- DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED
1147
+ DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED,
1148
+ DD_TEST_MANAGEMENT_ENABLED,
1149
+ DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES
1141
1150
  } = process.env
1142
1151
 
1143
1152
  if (DD_CIVISIBILITY_AGENTLESS_URL) {
@@ -1156,6 +1165,12 @@ class Config {
1156
1165
  this._setString(calc, 'ciVisibilityTestSessionName', DD_TEST_SESSION_NAME)
1157
1166
  this._setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', isTrue(DD_AGENTLESS_LOG_SUBMISSION_ENABLED))
1158
1167
  this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', isTrue(DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED))
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
+ )
1159
1174
  }
1160
1175
  this._setString(calc, 'dogstatsd.hostname', this._getHostname())
1161
1176
  this._setBoolean(calc, 'isGitUploadEnabled',
@@ -2,14 +2,14 @@ const { storage } = require('../../datadog-core')
2
2
  const log = require('./log')
3
3
 
4
4
  function getDataStreamsContext () {
5
- const store = storage.getStore()
5
+ const store = storage('legacy').getStore()
6
6
  return (store && store.dataStreamsContext) || null
7
7
  }
8
8
 
9
9
  function setDataStreamsContext (dataStreamsContext) {
10
10
  log.debug(() => `Setting new DSM Context: ${JSON.stringify(dataStreamsContext)}.`)
11
11
 
12
- if (dataStreamsContext) storage.enterWith({ ...(storage.getStore()), dataStreamsContext })
12
+ if (dataStreamsContext) storage('legacy').enterWith({ ...(storage('legacy').getStore()), dataStreamsContext })
13
13
  }
14
14
 
15
15
  module.exports = {
@@ -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
  })
@@ -26,7 +26,7 @@ function createAgentClass (BaseAgent) {
26
26
  }
27
27
 
28
28
  _noop (callback) {
29
- return storage.run({ noop: true }, callback)
29
+ return storage('legacy').run({ noop: true }, callback)
30
30
  }
31
31
  }
32
32
 
@@ -126,9 +126,9 @@ function request (data, options, callback) {
126
126
 
127
127
  activeRequests++
128
128
 
129
- const store = storage.getStore()
129
+ const store = storage('legacy').getStore()
130
130
 
131
- storage.enterWith({ noop: true })
131
+ storage('legacy').enterWith({ noop: true })
132
132
 
133
133
  const req = client.request(options, onResponse)
134
134
 
@@ -146,7 +146,7 @@ function request (data, options, callback) {
146
146
  req.end()
147
147
  }
148
148
 
149
- storage.enterWith(store)
149
+ storage('legacy').enterWith(store)
150
150
  }
151
151
 
152
152
  // TODO: Figure out why setTimeout is needed to avoid losing the async context
@@ -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) {
@@ -29,7 +29,7 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
29
29
  if (modelName.includes('embed')) {
30
30
  return
31
31
  }
32
- const span = storage.getStore()?.span
32
+ const span = storage('legacy').getStore()?.span
33
33
  this.setLLMObsTags({ request, span, response, modelProvider, modelName })
34
34
  })
35
35
 
@@ -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) {
@@ -15,11 +15,11 @@ let logger = defaultLogger
15
15
  let logChannel = new LogChannel()
16
16
 
17
17
  function withNoop (fn) {
18
- const store = storage.getStore()
18
+ const store = storage('legacy').getStore()
19
19
 
20
- storage.enterWith({ noop: true })
20
+ storage('legacy').enterWith({ noop: true })
21
21
  fn()
22
- storage.enterWith(store)
22
+ storage('legacy').enterWith(store)
23
23
  }
24
24
 
25
25
  function unsubscribeAll () {
@@ -6,7 +6,7 @@ const { storage } = require('../../../datadog-core') // TODO: noop storage?
6
6
 
7
7
  class NoopSpan {
8
8
  constructor (tracer, parent) {
9
- this._store = storage.getHandle()
9
+ this._store = storage('legacy').getHandle()
10
10
  this._noopTracer = tracer
11
11
  this._noopContext = this._createContext(parent)
12
12
  }
@@ -325,6 +325,7 @@ class TextMapPropagator {
325
325
  if (context === null) {
326
326
  context = extractedContext
327
327
  if (this._config.tracePropagationExtractFirst) {
328
+ this._extractBaggageItems(carrier, context)
328
329
  return context
329
330
  }
330
331
  } else {
@@ -344,10 +345,7 @@ class TextMapPropagator {
344
345
  }
345
346
  }
346
347
 
347
- if (this._hasPropagationStyle('extract', 'baggage') && carrier.baggage) {
348
- context = context || new DatadogSpanContext()
349
- this._extractBaggageItems(carrier, context)
350
- }
348
+ this._extractBaggageItems(carrier, context)
351
349
 
352
350
  return context || this._extractSqsdContext(carrier)
353
351
  }
@@ -596,6 +594,9 @@ class TextMapPropagator {
596
594
  }
597
595
 
598
596
  _extractBaggageItems (carrier, spanContext) {
597
+ if (!this._hasPropagationStyle('extract', 'baggage')) return
598
+ if (!carrier || !carrier.baggage) return
599
+ if (!spanContext) return
599
600
  const baggages = carrier.baggage.split(',')
600
601
  for (const keyValue of baggages) {
601
602
  if (!keyValue.includes('=')) {
@@ -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')
@@ -65,7 +65,7 @@ class DatadogSpan {
65
65
  this._debug = debug
66
66
  this._processor = processor
67
67
  this._prioritySampler = prioritySampler
68
- this._store = storage.getHandle()
68
+ this._store = storage('legacy').getHandle()
69
69
  this._duration = undefined
70
70
 
71
71
  this._events = []
@@ -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}`)
@@ -143,6 +143,7 @@ module.exports = class PluginManager {
143
143
  ciVisibilityTestSessionName,
144
144
  ciVisAgentlessLogSubmissionEnabled,
145
145
  isTestDynamicInstrumentationEnabled,
146
+ isServiceUserProvided,
146
147
  middlewareTracingEnabled
147
148
  } = this._tracerConfig
148
149
 
@@ -155,7 +156,8 @@ module.exports = class PluginManager {
155
156
  headers: headerTags || [],
156
157
  ciVisibilityTestSessionName,
157
158
  ciVisAgentlessLogSubmissionEnabled,
158
- isTestDynamicInstrumentationEnabled
159
+ isTestDynamicInstrumentationEnabled,
160
+ isServiceUserProvided
159
161
  }
160
162
 
161
163
  if (logInjection !== undefined) {
@@ -7,7 +7,7 @@ class ApolloBasePlugin extends TracingPlugin {
7
7
  static get kind () { return 'server' }
8
8
 
9
9
  bindStart (ctx) {
10
- const store = storage.getStore()
10
+ const store = storage('legacy').getStore()
11
11
  const childOf = store ? store.span : null
12
12
 
13
13
  const span = this.startSpan(this.getOperationName(), {