dd-trace 5.34.0 → 5.36.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 (97) hide show
  1. package/index.d.ts +3 -7
  2. package/package.json +4 -4
  3. package/packages/datadog-core/index.js +1 -1
  4. package/packages/datadog-core/src/storage.js +76 -31
  5. package/packages/datadog-instrumentations/src/aws-sdk.js +16 -0
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  7. package/packages/datadog-instrumentations/src/jest.js +3 -7
  8. package/packages/datadog-instrumentations/src/passport.js +45 -0
  9. package/packages/datadog-plugin-aerospike/src/index.js +1 -1
  10. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
  11. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  12. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
  13. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
  14. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +33 -6
  15. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
  16. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
  17. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  18. package/packages/datadog-plugin-couchbase/src/index.js +2 -2
  19. package/packages/datadog-plugin-cucumber/src/index.js +11 -11
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -1
  21. package/packages/datadog-plugin-cypress/src/support.js +36 -29
  22. package/packages/datadog-plugin-dd-trace-api/src/index.js +120 -0
  23. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  24. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  25. package/packages/datadog-plugin-hapi/src/index.js +1 -1
  26. package/packages/datadog-plugin-http/src/client.js +1 -1
  27. package/packages/datadog-plugin-http/src/server.js +1 -1
  28. package/packages/datadog-plugin-http2/src/client.js +3 -3
  29. package/packages/datadog-plugin-http2/src/server.js +1 -1
  30. package/packages/datadog-plugin-jest/src/index.js +6 -11
  31. package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
  32. package/packages/datadog-plugin-mariadb/src/index.js +3 -3
  33. package/packages/datadog-plugin-mocha/src/index.js +13 -13
  34. package/packages/datadog-plugin-next/src/index.js +4 -4
  35. package/packages/datadog-plugin-openai/src/tracing.js +1 -1
  36. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  37. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  38. package/packages/datadog-plugin-router/src/index.js +2 -2
  39. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  40. package/packages/datadog-plugin-vitest/src/index.js +11 -11
  41. package/packages/dd-trace/src/appsec/blocking.js +4 -1
  42. package/packages/dd-trace/src/appsec/channels.js +1 -0
  43. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  44. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +6 -6
  46. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
  47. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +5 -5
  48. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +2 -2
  49. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
  50. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
  51. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
  52. package/packages/dd-trace/src/appsec/iast/index.js +2 -2
  53. package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
  54. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
  55. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
  58. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  60. package/packages/dd-trace/src/appsec/index.js +20 -3
  61. package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
  62. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
  63. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  64. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
  65. package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
  66. package/packages/dd-trace/src/appsec/reporter.js +3 -3
  67. package/packages/dd-trace/src/appsec/sdk/set_user.js +9 -0
  68. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -1
  69. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  70. package/packages/dd-trace/src/appsec/user_tracking.js +32 -6
  71. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
  73. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -10
  74. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
  75. package/packages/dd-trace/src/config.js +2 -0
  76. package/packages/dd-trace/src/data_streams_context.js +2 -2
  77. package/packages/dd-trace/src/debugger/devtools_client/state.js +8 -3
  78. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  79. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  80. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +49 -4
  81. package/packages/dd-trace/src/log/writer.js +3 -3
  82. package/packages/dd-trace/src/noop/span.js +1 -1
  83. package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
  84. package/packages/dd-trace/src/opentracing/span.js +1 -1
  85. package/packages/dd-trace/src/plugin_manager.js +6 -1
  86. package/packages/dd-trace/src/plugins/apollo.js +1 -1
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +35 -1
  88. package/packages/dd-trace/src/plugins/index.js +1 -0
  89. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  90. package/packages/dd-trace/src/plugins/plugin.js +8 -8
  91. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  92. package/packages/dd-trace/src/plugins/util/git.js +3 -3
  93. package/packages/dd-trace/src/plugins/util/test.js +5 -1
  94. package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  96. package/packages/dd-trace/src/scope.js +5 -5
  97. package/packages/dd-trace/src/tracer.js +0 -14
@@ -14,9 +14,9 @@ const enabledFor = {
14
14
 
15
15
  let fsPlugin
16
16
 
17
- function enterWith (fsProps, store = storage.getStore()) {
17
+ function enterWith (fsProps, store = storage('legacy').getStore()) {
18
18
  if (store && !store.fs?.opExcluded) {
19
- storage.enterWith({
19
+ storage('legacy').enterWith({
20
20
  ...store,
21
21
  fs: {
22
22
  ...store.fs,
@@ -42,7 +42,7 @@ class AppsecFsPlugin extends Plugin {
42
42
  }
43
43
 
44
44
  _onFsOperationStart () {
45
- const store = storage.getStore()
45
+ const store = storage('legacy').getStore()
46
46
  if (store) {
47
47
  enterWith({ root: store.fs?.root === undefined }, store)
48
48
  }
@@ -53,9 +53,9 @@ class AppsecFsPlugin extends Plugin {
53
53
  }
54
54
 
55
55
  _onFsOperationFinishOrRenderEnd () {
56
- const store = storage.getStore()
56
+ const store = storage('legacy').getStore()
57
57
  if (store?.fs?.parentStore) {
58
- storage.enterWith(store.fs.parentStore)
58
+ storage('legacy').enterWith(store.fs.parentStore)
59
59
  }
60
60
  }
61
61
  }
@@ -47,7 +47,7 @@ function onFirstReceivedRequest () {
47
47
  }
48
48
 
49
49
  function analyzeLfi (ctx) {
50
- const store = storage.getStore()
50
+ const store = storage('legacy').getStore()
51
51
  if (!store) return
52
52
 
53
53
  const { req, fs, res } = store
@@ -49,7 +49,7 @@ function analyzePgSqlInjection (ctx) {
49
49
  }
50
50
 
51
51
  function analyzeSqlInjection (query, dbSystem, abortController) {
52
- const store = storage.getStore()
52
+ const store = storage('legacy').getStore()
53
53
  if (!store) return
54
54
 
55
55
  const { req, res } = store
@@ -91,7 +91,7 @@ function hasAddressesObjectInputAddress (addressesObject) {
91
91
  function clearQuerySet ({ payload }) {
92
92
  if (!payload) return
93
93
 
94
- const store = storage.getStore()
94
+ const store = storage('legacy').getStore()
95
95
  if (!store) return
96
96
 
97
97
  const { req } = store
@@ -19,7 +19,7 @@ function disable () {
19
19
  }
20
20
 
21
21
  function analyzeSsrf (ctx) {
22
- const store = storage.getStore()
22
+ const store = storage('legacy').getStore()
23
23
  const req = store?.req
24
24
  const outgoingUrl = (ctx.args.options?.uri && format(ctx.args.options.uri)) ?? ctx.args.uri
25
25
 
@@ -102,7 +102,7 @@ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
102
102
  }
103
103
 
104
104
  function reportMetrics (metrics, raspRule) {
105
- const store = storage.getStore()
105
+ const store = storage('legacy').getStore()
106
106
  const rootSpan = store?.req && web.root(store.req)
107
107
  if (!rootSpan) return
108
108
 
@@ -117,7 +117,7 @@ function reportMetrics (metrics, raspRule) {
117
117
  }
118
118
 
119
119
  function reportAttack (attackData) {
120
- const store = storage.getStore()
120
+ const store = storage('legacy').getStore()
121
121
  const req = store?.req
122
122
  const rootSpan = web.root(req)
123
123
  if (!rootSpan) return
@@ -162,7 +162,7 @@ function isFingerprintDerivative (derivative) {
162
162
  function reportDerivatives (derivatives) {
163
163
  if (!derivatives) return
164
164
 
165
- const req = storage.getStore()?.req
165
+ const req = storage('legacy').getStore()?.req
166
166
  const rootSpan = web.root(req)
167
167
 
168
168
  if (!rootSpan) return
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { getRootSpan } = require('./utils')
4
4
  const log = require('../../log')
5
+ const waf = require('../waf')
6
+ const addresses = require('../addresses')
5
7
 
6
8
  function setUserTags (user, rootSpan) {
7
9
  for (const k of Object.keys(user)) {
@@ -22,6 +24,13 @@ function setUser (tracer, user) {
22
24
  }
23
25
 
24
26
  setUserTags(user, rootSpan)
27
+ rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')
28
+
29
+ waf.run({
30
+ persistent: {
31
+ [addresses.USER_ID]: '' + user.id
32
+ }
33
+ })
25
34
  }
26
35
 
27
36
  module.exports = {
@@ -23,6 +23,7 @@ function checkUserAndSetUser (tracer, user) {
23
23
  if (rootSpan) {
24
24
  if (!rootSpan.context()._tags['usr.id']) {
25
25
  setUserTags(user, rootSpan)
26
+ rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')
26
27
  }
27
28
  } else {
28
29
  log.warn('[ASM] Root span not available in isUserBlocked')
@@ -33,7 +34,7 @@ function checkUserAndSetUser (tracer, user) {
33
34
 
34
35
  function blockRequest (tracer, req, res) {
35
36
  if (!req || !res) {
36
- const store = storage.getStore()
37
+ const store = storage('legacy').getStore()
37
38
  if (store) {
38
39
  req = req || store.req
39
40
  res = res || store.res
@@ -186,6 +186,15 @@ function incrementMissingUserLoginMetric (framework, eventType) {
186
186
  }).inc()
187
187
  }
188
188
 
189
+ function incrementMissingUserIdMetric (framework, eventType) {
190
+ if (!enabled) return
191
+
192
+ appsecMetrics.count('instrum.user_auth.missing_user_id', {
193
+ framework,
194
+ event_type: eventType
195
+ }).inc()
196
+ }
197
+
189
198
  function getRequestMetrics (req) {
190
199
  if (req) {
191
200
  const store = getStore(req)
@@ -203,6 +212,7 @@ module.exports = {
203
212
  incrementWafUpdatesMetric,
204
213
  incrementWafRequestsMetric,
205
214
  incrementMissingUserLoginMetric,
215
+ incrementMissingUserIdMetric,
206
216
 
207
217
  getRequestMetrics
208
218
  }
@@ -53,6 +53,8 @@ function obfuscateIfNeeded (str) {
53
53
  function getUserId (user) {
54
54
  if (!user) return
55
55
 
56
+ // should we iterate on user keys instead to be case insensitive ?
57
+ // but if we iterate over user then we're missing the inherited props ?
56
58
  for (const field of USER_ID_FIELDS) {
57
59
  let id = user[field]
58
60
 
@@ -73,11 +75,6 @@ function getUserId (user) {
73
75
  function trackLogin (framework, login, user, success, rootSpan) {
74
76
  if (!collectionMode || collectionMode === 'disabled') return
75
77
 
76
- if (!rootSpan) {
77
- log.error('[ASM] No rootSpan found in AppSec trackLogin')
78
- return
79
- }
80
-
81
78
  if (typeof login !== 'string') {
82
79
  log.error('[ASM] Invalid login provided to AppSec trackLogin')
83
80
 
@@ -162,7 +159,36 @@ function trackLogin (framework, login, user, success, rootSpan) {
162
159
  return waf.run({ persistent })
163
160
  }
164
161
 
162
+ function trackUser (user, rootSpan) {
163
+ if (!collectionMode || collectionMode === 'disabled') return
164
+
165
+ const userId = getUserId(user)
166
+ if (!userId) {
167
+ log.error('[ASM] No valid user ID found in AppSec trackUser')
168
+ telemetry.incrementMissingUserIdMetric('passport', 'authenticated_request')
169
+ return
170
+ }
171
+
172
+ rootSpan.setTag('_dd.appsec.usr.id', userId)
173
+
174
+ const isSdkCalled = rootSpan.context()._tags['_dd.appsec.user.collection_mode'] === 'sdk'
175
+ // do not override SDK
176
+ if (!isSdkCalled) {
177
+ rootSpan.addTags({
178
+ 'usr.id': userId,
179
+ '_dd.appsec.user.collection_mode': collectionMode
180
+ })
181
+
182
+ return waf.run({
183
+ persistent: {
184
+ [addresses.USER_ID]: userId
185
+ }
186
+ })
187
+ }
188
+ }
189
+
165
190
  module.exports = {
166
191
  setCollectionMode,
167
- trackLogin
192
+ trackLogin,
193
+ trackUser
168
194
  }
@@ -48,7 +48,7 @@ function update (newRules) {
48
48
 
49
49
  function run (data, req, raspRule) {
50
50
  if (!req) {
51
- const store = storage.getStore()
51
+ const store = storage('legacy').getStore()
52
52
  if (!store || !store.req) {
53
53
  log.warn('[ASM] Request object not available in waf.run')
54
54
  return
@@ -119,6 +119,8 @@ class TestVisDynamicInstrumentation {
119
119
  const onHit = this.onHitBreakpointByProbeId.get(probeId)
120
120
  if (onHit) {
121
121
  onHit({ snapshot })
122
+ } else {
123
+ log.warn('Received a breakpoint hit for an unknown probe')
122
124
  }
123
125
  }).unref()
124
126
 
@@ -93,11 +93,14 @@ async function addBreakpoint (probe) {
93
93
  probe.location = { file, lines: [String(line)] }
94
94
 
95
95
  const script = findScriptFromPartialPath(file)
96
- if (!script) throw new Error(`No loaded script found for ${file}`)
96
+ if (!script) {
97
+ log.error(`No loaded script found for ${file}`)
98
+ throw new Error(`No loaded script found for ${file}`)
99
+ }
97
100
 
98
101
  const [path, scriptId, sourceMapURL] = script
99
102
 
100
- log.debug(`Adding breakpoint at ${path}:${line}`)
103
+ log.warn(`Adding breakpoint at ${path}:${line}`)
101
104
 
102
105
  let lineNumber = line
103
106
 
@@ -109,15 +112,19 @@ async function addBreakpoint (probe) {
109
112
  }
110
113
  }
111
114
 
112
- const { breakpointId } = await session.post('Debugger.setBreakpoint', {
113
- location: {
114
- scriptId,
115
- lineNumber: lineNumber - 1
116
- }
117
- })
115
+ try {
116
+ const { breakpointId } = await session.post('Debugger.setBreakpoint', {
117
+ location: {
118
+ scriptId,
119
+ lineNumber: lineNumber - 1
120
+ }
121
+ })
118
122
 
119
- breakpointIdToProbe.set(breakpointId, probe)
120
- probeIdToBreakpointId.set(probe.id, breakpointId)
123
+ breakpointIdToProbe.set(breakpointId, probe)
124
+ probeIdToBreakpointId.set(probe.id, breakpointId)
125
+ } catch (e) {
126
+ log.error(`Error setting breakpoint at ${path}:${line}:`, e)
127
+ }
121
128
  }
122
129
 
123
130
  function start () {
@@ -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)
@@ -518,6 +518,7 @@ class Config {
518
518
  this._setValue(defaults, 'ciVisAgentlessLogSubmissionEnabled', false)
519
519
  this._setValue(defaults, 'legacyBaggageEnabled', true)
520
520
  this._setValue(defaults, 'isTestDynamicInstrumentationEnabled', false)
521
+ this._setValue(defaults, 'isServiceUserProvided', false)
521
522
  this._setValue(defaults, 'logInjection', false)
522
523
  this._setValue(defaults, 'lookup', undefined)
523
524
  this._setValue(defaults, 'inferredProxyServicesEnabled', false)
@@ -1156,6 +1157,7 @@ class Config {
1156
1157
  this._setString(calc, 'ciVisibilityTestSessionName', DD_TEST_SESSION_NAME)
1157
1158
  this._setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', isTrue(DD_AGENTLESS_LOG_SUBMISSION_ENABLED))
1158
1159
  this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', isTrue(DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED))
1160
+ this._setBoolean(calc, 'isServiceUserProvided', !!this._env.service)
1159
1161
  }
1160
1162
  this._setString(calc, 'dogstatsd.hostname', this._getHostname())
1161
1163
  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 = {
@@ -21,6 +21,8 @@ module.exports = {
21
21
  findScriptFromPartialPath (path) {
22
22
  if (!path) return null // This shouldn't happen, but better safe than sorry
23
23
 
24
+ path = path.toLowerCase()
25
+
24
26
  const bestMatch = new Array(3)
25
27
  let maxMatchLength = -1
26
28
 
@@ -33,7 +35,7 @@ module.exports = {
33
35
 
34
36
  // Compare characters from the end
35
37
  while (i >= 0 && j >= 0) {
36
- const urlChar = url[i]
38
+ const urlChar = url[i].toLowerCase()
37
39
  const pathChar = path[j]
38
40
 
39
41
  // Check if both characters is a path boundary
@@ -43,8 +45,8 @@ module.exports = {
43
45
  // If both are boundaries, or if characters match exactly
44
46
  if (isBoundary || urlChar === pathChar) {
45
47
  if (isBoundary) {
46
- lastBoundaryPos = matchLength
47
48
  atBoundary = true
49
+ lastBoundaryPos = matchLength
48
50
  } else {
49
51
  atBoundary = false
50
52
  }
@@ -71,7 +73,10 @@ module.exports = {
71
73
  }
72
74
 
73
75
  // If we found a valid match and it's better than our previous best
74
- if (atBoundary && lastBoundaryPos !== -1 && lastBoundaryPos > maxMatchLength) {
76
+ if (atBoundary && (
77
+ lastBoundaryPos > maxMatchLength ||
78
+ (lastBoundaryPos === maxMatchLength && url.length < bestMatch[0].length) // Prefer shorter paths
79
+ )) {
75
80
  maxMatchLength = lastBoundaryPos
76
81
  bestMatch[0] = url
77
82
  bestMatch[1] = scriptId
@@ -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
@@ -8,7 +8,9 @@ const {
8
8
  parseModelId
9
9
  } = require('../../../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
10
10
 
11
- const enabledOperations = ['invokeModel']
11
+ const ENABLED_OPERATIONS = ['invokeModel']
12
+
13
+ const requestIdsToTokens = {}
12
14
 
13
15
  class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
14
16
  constructor () {
@@ -18,7 +20,7 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
18
20
  const request = response.request
19
21
  const operation = request.operation
20
22
  // avoids instrumenting other non supported runtime operations
21
- if (!enabledOperations.includes(operation)) {
23
+ if (!ENABLED_OPERATIONS.includes(operation)) {
22
24
  return
23
25
  }
24
26
  const { modelProvider, modelName } = parseModelId(request.params.modelId)
@@ -27,9 +29,20 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
27
29
  if (modelName.includes('embed')) {
28
30
  return
29
31
  }
30
- const span = storage.getStore()?.span
32
+ const span = storage('legacy').getStore()?.span
31
33
  this.setLLMObsTags({ request, span, response, modelProvider, modelName })
32
34
  })
35
+
36
+ this.addSub('apm:aws:response:deserialize:bedrockruntime', ({ headers }) => {
37
+ const requestId = headers['x-amzn-requestid']
38
+ const inputTokenCount = headers['x-amzn-bedrock-input-token-count']
39
+ const outputTokenCount = headers['x-amzn-bedrock-output-token-count']
40
+
41
+ requestIdsToTokens[requestId] = {
42
+ inputTokensFromHeaders: inputTokenCount && parseInt(inputTokenCount),
43
+ outputTokensFromHeaders: outputTokenCount && parseInt(outputTokenCount)
44
+ }
45
+ })
33
46
  }
34
47
 
35
48
  setLLMObsTags ({ request, span, response, modelProvider, modelName }) {
@@ -52,7 +65,39 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
52
65
  })
53
66
 
54
67
  // add I/O tags
55
- this._tagger.tagLLMIO(span, requestParams.prompt, textAndResponseReason.message)
68
+ this._tagger.tagLLMIO(
69
+ span,
70
+ requestParams.prompt,
71
+ [{ content: textAndResponseReason.message, role: textAndResponseReason.role }]
72
+ )
73
+
74
+ // add token metrics
75
+ const { inputTokens, outputTokens, totalTokens } = extractTokens({
76
+ requestId: response.$metadata.requestId,
77
+ usage: textAndResponseReason.usage
78
+ })
79
+ this._tagger.tagMetrics(span, {
80
+ inputTokens,
81
+ outputTokens,
82
+ totalTokens
83
+ })
84
+ }
85
+ }
86
+
87
+ function extractTokens ({ requestId, usage }) {
88
+ const {
89
+ inputTokensFromHeaders,
90
+ outputTokensFromHeaders
91
+ } = requestIdsToTokens[requestId] || {}
92
+ delete requestIdsToTokens[requestId]
93
+
94
+ const inputTokens = usage.inputTokens || inputTokensFromHeaders || 0
95
+ const outputTokens = usage.outputTokens || outputTokensFromHeaders || 0
96
+
97
+ return {
98
+ inputTokens,
99
+ outputTokens,
100
+ totalTokens: inputTokens + outputTokens
56
101
  }
57
102
  }
58
103
 
@@ -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('=')) {
@@ -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 = []
@@ -31,6 +31,9 @@ loadChannel.subscribe(({ name }) => {
31
31
  // Globals
32
32
  maybeEnable(require('../../datadog-plugin-fetch/src'))
33
33
 
34
+ // Always enabled
35
+ maybeEnable(require('../../datadog-plugin-dd-trace-api/src'))
36
+
34
37
  function maybeEnable (Plugin) {
35
38
  if (!Plugin || typeof Plugin !== 'function') return
36
39
  if (!pluginClasses[Plugin.id]) {
@@ -140,6 +143,7 @@ module.exports = class PluginManager {
140
143
  ciVisibilityTestSessionName,
141
144
  ciVisAgentlessLogSubmissionEnabled,
142
145
  isTestDynamicInstrumentationEnabled,
146
+ isServiceUserProvided,
143
147
  middlewareTracingEnabled
144
148
  } = this._tracerConfig
145
149
 
@@ -152,7 +156,8 @@ module.exports = class PluginManager {
152
156
  headers: headerTags || [],
153
157
  ciVisibilityTestSessionName,
154
158
  ciVisAgentlessLogSubmissionEnabled,
155
- isTestDynamicInstrumentationEnabled
159
+ isTestDynamicInstrumentationEnabled,
160
+ isServiceUserProvided
156
161
  }
157
162
 
158
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(), {
@@ -45,6 +45,7 @@ module.exports = class CiPlugin extends Plugin {
45
45
  constructor (...args) {
46
46
  super(...args)
47
47
 
48
+ this.fileLineToProbeId = new Map()
48
49
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
49
50
 
50
51
  this.addSub(`ci:${this.constructor.id}:library-configuration`, ({ onDone }) => {
@@ -335,7 +336,22 @@ module.exports = class CiPlugin extends Plugin {
335
336
  })
336
337
  }
337
338
 
338
- removeDiProbe (probeId) {
339
+ removeAllDiProbes () {
340
+ if (this.fileLineToProbeId.size === 0) {
341
+ return Promise.resolve()
342
+ }
343
+ log.debug('Removing all Dynamic Instrumentation probes')
344
+ return Promise.all(Array.from(this.fileLineToProbeId.keys())
345
+ .map((fileLine) => {
346
+ const [file, line] = fileLine.split(':')
347
+ return this.removeDiProbe({ file, line })
348
+ }))
349
+ }
350
+
351
+ removeDiProbe ({ file, line }) {
352
+ const probeId = this.fileLineToProbeId.get(`${file}:${line}`)
353
+ log.warn(`Removing probe from ${file}:${line}, with id: ${probeId}`)
354
+ this.fileLineToProbeId.delete(probeId)
339
355
  return this.di.removeProbe(probeId)
340
356
  }
341
357
 
@@ -346,9 +362,27 @@ module.exports = class CiPlugin extends Plugin {
346
362
  log.warn('Could not add breakpoint for dynamic instrumentation')
347
363
  return
348
364
  }
365
+ log.debug('Adding breakpoint for Dynamic Instrumentation')
366
+
367
+ this.testErrorStackIndex = stackIndex
368
+ const activeProbeKey = `${file}:${line}`
369
+
370
+ if (this.fileLineToProbeId.has(activeProbeKey)) {
371
+ log.warn('Probe already set for this line')
372
+ const oldProbeId = this.fileLineToProbeId.get(activeProbeKey)
373
+ return {
374
+ probeId: oldProbeId,
375
+ setProbePromise: Promise.resolve(),
376
+ stackIndex,
377
+ file,
378
+ line
379
+ }
380
+ }
349
381
 
350
382
  const [probeId, setProbePromise] = this.di.addLineProbe({ file, line }, this.onDiBreakpointHit.bind(this))
351
383
 
384
+ this.fileLineToProbeId.set(activeProbeKey, probeId)
385
+
352
386
  return {
353
387
  probeId,
354
388
  setProbePromise,