dd-trace 4.51.1 → 4.53.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 (172) hide show
  1. package/LICENSE-3rdparty.csv +8 -2
  2. package/ci/init.js +16 -0
  3. package/index.d.ts +31 -13
  4. package/init.js +4 -66
  5. package/initialize.mjs +13 -10
  6. package/loader-hook.mjs +4 -0
  7. package/package.json +16 -11
  8. package/packages/datadog-core/src/storage.js +39 -2
  9. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  10. package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
  11. package/packages/datadog-instrumentations/src/cucumber.js +29 -3
  12. package/packages/datadog-instrumentations/src/express.js +38 -4
  13. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
  14. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
  15. package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
  16. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  17. package/packages/datadog-instrumentations/src/jest.js +27 -8
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  19. package/packages/datadog-instrumentations/src/mysql2.js +13 -8
  20. package/packages/datadog-instrumentations/src/next.js +7 -4
  21. package/packages/datadog-instrumentations/src/passport-http.js +2 -14
  22. package/packages/datadog-instrumentations/src/passport-local.js +2 -14
  23. package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
  24. package/packages/datadog-instrumentations/src/pg.js +6 -6
  25. package/packages/datadog-instrumentations/src/playwright.js +17 -4
  26. package/packages/datadog-instrumentations/src/router.js +97 -1
  27. package/packages/datadog-instrumentations/src/sequelize.js +9 -4
  28. package/packages/datadog-instrumentations/src/url.js +4 -0
  29. package/packages/datadog-instrumentations/src/vitest.js +27 -2
  30. package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
  31. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
  32. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  35. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  36. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  37. package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
  38. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  39. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  40. package/packages/datadog-plugin-cucumber/src/index.js +39 -4
  41. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
  42. package/packages/datadog-plugin-grpc/src/client.js +2 -2
  43. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +39 -4
  45. package/packages/datadog-plugin-langchain/src/handlers/language_models/chat_model.js +1 -1
  46. package/packages/datadog-plugin-langchain/src/handlers/language_models/llm.js +1 -1
  47. package/packages/datadog-plugin-mocha/src/index.js +36 -2
  48. package/packages/datadog-plugin-oracledb/src/index.js +1 -1
  49. package/packages/datadog-plugin-vitest/src/index.js +34 -2
  50. package/packages/datadog-shimmer/src/shimmer.js +8 -4
  51. package/packages/dd-trace/src/appsec/addresses.js +3 -0
  52. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  53. package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
  54. package/packages/dd-trace/src/appsec/blocking.js +1 -1
  55. package/packages/dd-trace/src/appsec/channels.js +1 -0
  56. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
  57. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +2 -2
  58. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  59. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
  60. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
  61. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
  62. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
  63. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
  64. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +8 -21
  65. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +2 -2
  67. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
  68. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +5 -8
  69. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  70. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +7 -11
  71. package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +2 -3
  72. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +2 -2
  73. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +2 -2
  74. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +2 -2
  75. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
  76. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -3
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  78. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  79. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
  80. package/packages/dd-trace/src/appsec/index.js +18 -13
  81. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +2 -2
  82. package/packages/dd-trace/src/appsec/rasp/utils.js +1 -1
  83. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  84. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
  85. package/packages/dd-trace/src/appsec/remote_config/manager.js +2 -2
  86. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  87. package/packages/dd-trace/src/appsec/sdk/set_user.js +2 -2
  88. package/packages/dd-trace/src/appsec/sdk/track_event.js +37 -24
  89. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -4
  90. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  91. package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
  92. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  93. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -3
  94. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  95. package/packages/dd-trace/src/azure_metadata.js +4 -4
  96. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
  97. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  101. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
  103. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  104. package/packages/dd-trace/src/config.js +24 -32
  105. package/packages/dd-trace/src/constants.js +1 -0
  106. package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
  107. package/packages/dd-trace/src/datastreams/processor.js +4 -6
  108. package/packages/dd-trace/src/datastreams/writer.js +6 -5
  109. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
  110. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  111. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
  112. package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
  113. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
  114. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
  116. package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
  117. package/packages/dd-trace/src/debugger/index.js +14 -10
  118. package/packages/dd-trace/src/dogstatsd.js +2 -2
  119. package/packages/dd-trace/src/encode/0.4.js +23 -78
  120. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
  121. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
  122. package/packages/dd-trace/src/encode/span-stats.js +0 -30
  123. package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
  124. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  125. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
  126. package/packages/dd-trace/src/flare/index.js +1 -1
  127. package/packages/dd-trace/src/guardrails/index.js +64 -0
  128. package/packages/dd-trace/src/guardrails/log.js +32 -0
  129. package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
  130. package/packages/dd-trace/src/guardrails/util.js +10 -0
  131. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  132. package/packages/dd-trace/src/llmobs/storage.js +2 -3
  133. package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
  134. package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
  135. package/packages/dd-trace/src/msgpack/encoder.js +309 -0
  136. package/packages/dd-trace/src/msgpack/index.js +6 -0
  137. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
  138. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
  139. package/packages/dd-trace/src/opentracing/span.js +1 -1
  140. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  141. package/packages/dd-trace/src/plugin_manager.js +4 -2
  142. package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
  143. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  144. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  145. package/packages/dd-trace/src/plugins/util/git.js +7 -7
  146. package/packages/dd-trace/src/plugins/util/test.js +36 -3
  147. package/packages/dd-trace/src/plugins/util/web.js +2 -2
  148. package/packages/dd-trace/src/profiling/config.js +3 -0
  149. package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
  150. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
  151. package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
  152. package/packages/dd-trace/src/profiling/profiler.js +62 -10
  153. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
  154. package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
  155. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
  156. package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
  157. package/packages/dd-trace/src/proxy.js +7 -2
  158. package/packages/dd-trace/src/runtime_metrics.js +107 -4
  159. package/packages/dd-trace/src/serverless.js +1 -1
  160. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +1 -1
  161. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +1 -1
  162. package/packages/dd-trace/src/span_processor.js +10 -10
  163. package/packages/dd-trace/src/tagger.js +1 -1
  164. package/packages/dd-trace/src/telemetry/index.js +1 -0
  165. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  166. package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
  167. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  168. package/packages/dd-trace/src/util.js +5 -16
  169. package/packages/datadog-instrumentations/src/qs.js +0 -24
  170. package/packages/dd-trace/src/appsec/iast/iast-log.js +0 -86
  171. package/packages/dd-trace/src/appsec/passport.js +0 -110
  172. package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
@@ -117,6 +117,7 @@ function getSortWrapper (sort) {
117
117
  let isEarlyFlakeDetectionEnabled = false
118
118
  let earlyFlakeDetectionNumRetries = 0
119
119
  let isEarlyFlakeDetectionFaulty = false
120
+ let isDiEnabled = false
120
121
  let knownTests = {}
121
122
 
122
123
  try {
@@ -126,10 +127,12 @@ function getSortWrapper (sort) {
126
127
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
127
128
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
128
129
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
130
+ isDiEnabled = libraryConfig.isDiEnabled
129
131
  }
130
132
  } catch (e) {
131
133
  isFlakyTestRetriesEnabled = false
132
134
  isEarlyFlakeDetectionEnabled = false
135
+ isDiEnabled = false
133
136
  }
134
137
 
135
138
  if (isFlakyTestRetriesEnabled && !this.ctx.config.retry && flakyTestRetriesCount > 0) {
@@ -169,6 +172,15 @@ function getSortWrapper (sort) {
169
172
  }
170
173
  }
171
174
 
175
+ if (isDiEnabled) {
176
+ try {
177
+ const workspaceProject = this.ctx.getCoreWorkspaceProject()
178
+ workspaceProject._provided._ddIsDiEnabled = isDiEnabled
179
+ } catch (e) {
180
+ log.warn('Could not send Dynamic Instrumentation configuration to workers.')
181
+ }
182
+ }
183
+
172
184
  let testCodeCoverageLinesTotal
173
185
 
174
186
  if (this.ctx.coverageProvider?.generateCoverage) {
@@ -298,13 +310,16 @@ addHook({
298
310
  const testName = getTestName(task)
299
311
  let isNew = false
300
312
  let isEarlyFlakeDetectionEnabled = false
313
+ let isDiEnabled = false
301
314
 
302
315
  try {
303
316
  const {
304
- _ddIsEarlyFlakeDetectionEnabled
317
+ _ddIsEarlyFlakeDetectionEnabled,
318
+ _ddIsDiEnabled
305
319
  } = globalThis.__vitest_worker__.providedContext
306
320
 
307
321
  isEarlyFlakeDetectionEnabled = _ddIsEarlyFlakeDetectionEnabled
322
+ isDiEnabled = _ddIsDiEnabled
308
323
 
309
324
  if (isEarlyFlakeDetectionEnabled) {
310
325
  isNew = newTasks.has(task)
@@ -316,12 +331,22 @@ addHook({
316
331
 
317
332
  // We finish the previous test here because we know it has failed already
318
333
  if (numAttempt > 0) {
334
+ const probe = {}
319
335
  const asyncResource = taskToAsync.get(task)
320
336
  const testError = task.result?.errors?.[0]
321
337
  if (asyncResource) {
322
338
  asyncResource.runInAsyncScope(() => {
323
- testErrorCh.publish({ error: testError })
339
+ testErrorCh.publish({
340
+ error: testError,
341
+ willBeRetried: true,
342
+ probe,
343
+ isDiEnabled
344
+ })
324
345
  })
346
+ // We wait for the probe to be set
347
+ if (probe.setProbePromise) {
348
+ await probe.setProbePromise
349
+ }
325
350
  }
326
351
  }
327
352
 
@@ -108,10 +108,15 @@ class SchemaExtractor {
108
108
  if (!builder.shouldExtractSchema(schemaName, depth)) {
109
109
  return false
110
110
  }
111
- for (const field of schema.fields) {
112
- if (!this.extractProperty(field, schemaName, field.name, builder, depth)) {
113
- log.warn(`DSM: Unable to extract field with name: ${field.name} from Avro schema with name: ${schemaName}`)
111
+ if (schema.fields?.[Symbol.iterator]) {
112
+ for (const field of schema.fields) {
113
+ if (!this.extractProperty(field, schemaName, field.name, builder, depth)) {
114
+ log.warn('DSM: Unable to extract field with name: %s from Avro schema with name: %s', field.name,
115
+ schemaName)
116
+ }
114
117
  }
118
+ } else {
119
+ log.warn('DSM: schema.fields is not iterable from Avro schema with name: %s', schemaName)
115
120
  }
116
121
  }
117
122
  return true
@@ -1,6 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseAwsSdkPlugin = require('../base')
4
+ const log = require('../../../dd-trace/src/log')
5
+ const { DYNAMODB_PTR_KIND, SPAN_POINTER_DIRECTION } = require('../../../dd-trace/src/constants')
6
+ const { extractPrimaryKeys, generatePointerHash } = require('../util')
4
7
 
5
8
  class DynamoDb extends BaseAwsSdkPlugin {
6
9
  static get id () { return 'dynamodb' }
@@ -48,6 +51,157 @@ class DynamoDb extends BaseAwsSdkPlugin {
48
51
 
49
52
  return tags
50
53
  }
54
+
55
+ addSpanPointers (span, response) {
56
+ const request = response?.request
57
+ const operationName = request?.operation
58
+
59
+ const hashes = []
60
+ switch (operationName) {
61
+ case 'putItem': {
62
+ const hash = DynamoDb.calculatePutItemHash(
63
+ request?.params?.TableName,
64
+ request?.params?.Item,
65
+ this.getPrimaryKeyConfig()
66
+ )
67
+ if (hash) hashes.push(hash)
68
+ break
69
+ }
70
+ case 'updateItem':
71
+ case 'deleteItem': {
72
+ const hash = DynamoDb.calculateHashWithKnownKeys(request?.params?.TableName, request?.params?.Key)
73
+ if (hash) hashes.push(hash)
74
+ break
75
+ }
76
+ case 'transactWriteItems': {
77
+ const transactItems = request?.params?.TransactItems || []
78
+ for (const item of transactItems) {
79
+ if (item.Put) {
80
+ const hash =
81
+ DynamoDb.calculatePutItemHash(item.Put.TableName, item.Put.Item, this.getPrimaryKeyConfig())
82
+ if (hash) hashes.push(hash)
83
+ } else if (item.Update || item.Delete) {
84
+ const operation = item.Update ? item.Update : item.Delete
85
+ const hash = DynamoDb.calculateHashWithKnownKeys(operation.TableName, operation.Key)
86
+ if (hash) hashes.push(hash)
87
+ }
88
+ }
89
+ break
90
+ }
91
+ case 'batchWriteItem': {
92
+ const requestItems = request?.params.RequestItems || {}
93
+ for (const [tableName, operations] of Object.entries(requestItems)) {
94
+ if (!Array.isArray(operations)) continue
95
+ for (const operation of operations) {
96
+ if (operation?.PutRequest) {
97
+ const hash =
98
+ DynamoDb.calculatePutItemHash(tableName, operation.PutRequest.Item, this.getPrimaryKeyConfig())
99
+ if (hash) hashes.push(hash)
100
+ } else if (operation?.DeleteRequest) {
101
+ const hash = DynamoDb.calculateHashWithKnownKeys(tableName, operation.DeleteRequest.Key)
102
+ if (hash) hashes.push(hash)
103
+ }
104
+ }
105
+ }
106
+ break
107
+ }
108
+ }
109
+
110
+ for (const hash of hashes) {
111
+ span.addSpanPointer(DYNAMODB_PTR_KIND, SPAN_POINTER_DIRECTION.DOWNSTREAM, hash)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Parses primary key config from the `DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS` env var.
117
+ * Only runs when needed, and warns when missing or invalid config.
118
+ * @returns {Object|undefined} Parsed config from env var or undefined if empty/missing/invalid config.
119
+ */
120
+ getPrimaryKeyConfig () {
121
+ if (this.dynamoPrimaryKeyConfig) {
122
+ // Return cached config if it exists
123
+ return this.dynamoPrimaryKeyConfig
124
+ }
125
+
126
+ const configStr = this._tracerConfig?.aws?.dynamoDb?.tablePrimaryKeys
127
+ if (!configStr) {
128
+ log.warn('Missing DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS env variable. ' +
129
+ 'Please add your table\'s primary keys under this env variable.')
130
+ return
131
+ }
132
+
133
+ try {
134
+ const parsedConfig = JSON.parse(configStr)
135
+ const config = {}
136
+ for (const [tableName, primaryKeys] of Object.entries(parsedConfig)) {
137
+ if (Array.isArray(primaryKeys) && primaryKeys.length > 0 && primaryKeys.length <= 2) {
138
+ config[tableName] = new Set(primaryKeys)
139
+ } else {
140
+ log.warn(`Invalid primary key configuration for table: ${tableName}.` +
141
+ 'Please fix the DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS env var.')
142
+ }
143
+ }
144
+
145
+ this.dynamoPrimaryKeyConfig = config
146
+ return config
147
+ } catch (err) {
148
+ log.warn('Failed to parse DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS:', err.message)
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Calculates a hash for DynamoDB PutItem operations using table's configured primary keys.
154
+ * @param {string} tableName - Name of the DynamoDB table.
155
+ * @param {Object} item - Complete PutItem item parameter to be put.
156
+ * @param {Object.<string, Set<string>>} primaryKeyConfig - Mapping of table names to Sets of primary key names
157
+ * loaded from DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS.
158
+ * @returns {string|undefined} Hash combining table name and primary key/value pairs, or undefined if unable.
159
+ */
160
+ static calculatePutItemHash (tableName, item, primaryKeyConfig) {
161
+ if (!tableName || !item) {
162
+ log.debug('Unable to calculate hash because missing required parameters')
163
+ return
164
+ }
165
+ if (!primaryKeyConfig) {
166
+ log.warn('Missing DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS env variable')
167
+ return
168
+ }
169
+ const primaryKeySet = primaryKeyConfig[tableName]
170
+ if (!primaryKeySet || !(primaryKeySet instanceof Set) || primaryKeySet.size === 0 || primaryKeySet.size > 2) {
171
+ log.warn(
172
+ `span pointers: failed to extract PutItem span pointer: table ${tableName} ` +
173
+ 'not found in primary key names or the DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS env var was invalid.' +
174
+ 'Please update the env var.'
175
+ )
176
+ return
177
+ }
178
+ const keyValues = extractPrimaryKeys(primaryKeySet, item)
179
+ if (keyValues) {
180
+ return generatePointerHash([tableName, ...keyValues])
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Calculates a hash for DynamoDB operations that have keys provided (UpdateItem, DeleteItem).
186
+ * @param {string} tableName - Name of the DynamoDB table.
187
+ * @param {Object} keysObject - Object containing primary key/value attributes in DynamoDB format.
188
+ * (e.g., { userId: { S: "123" }, sortKey: { N: "456" } })
189
+ * @returns {string|undefined} Hash value combining table name and primary key/value pairs, or undefined if unable.
190
+ *
191
+ * @example
192
+ * calculateHashWithKnownKeys('UserTable', { userId: { S: "user123" }, timestamp: { N: "1234567" } })
193
+ */
194
+ static calculateHashWithKnownKeys (tableName, keysObject) {
195
+ if (!tableName || !keysObject) {
196
+ log.debug('Unable to calculate hash because missing parameters')
197
+ return
198
+ }
199
+ const keyNamesSet = new Set(Object.keys(keysObject))
200
+ const keyValues = extractPrimaryKeys(keyNamesSet, keysObject)
201
+ if (keyValues) {
202
+ return generatePointerHash([tableName, ...keyValues])
203
+ }
204
+ }
51
205
  }
52
206
 
53
207
  module.exports = DynamoDb
@@ -45,7 +45,7 @@ class EventBridge extends BaseAwsSdkPlugin {
45
45
  }
46
46
  request.params.Entries[0].Detail = finalData
47
47
  } catch (e) {
48
- log.error(e)
48
+ log.error('EventBridge error injecting request', e)
49
49
  }
50
50
  }
51
51
  }
@@ -97,7 +97,7 @@ class Kinesis extends BaseAwsSdkPlugin {
97
97
  parsedAttributes: decodedData._datadog
98
98
  }
99
99
  } catch (e) {
100
- log.error(e)
100
+ log.error('Kinesis error extracting response', e)
101
101
  }
102
102
  }
103
103
 
@@ -43,7 +43,7 @@ class Lambda extends BaseAwsSdkPlugin {
43
43
  const newContextBase64 = Buffer.from(JSON.stringify(clientContext)).toString('base64')
44
44
  request.params.ClientContext = newContextBase64
45
45
  } catch (err) {
46
- log.error(err)
46
+ log.error('Lambda error injecting request', err)
47
47
  }
48
48
  }
49
49
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const BaseAwsSdkPlugin = require('../base')
4
4
  const log = require('../../../dd-trace/src/log')
5
- const { generatePointerHash } = require('../../../dd-trace/src/util')
5
+ const { generatePointerHash } = require('../util')
6
6
  const { S3_PTR_KIND, SPAN_POINTER_DIRECTION } = require('../../../dd-trace/src/constants')
7
7
 
8
8
  class S3 extends BaseAwsSdkPlugin {
@@ -163,7 +163,7 @@ class Sqs extends BaseAwsSdkPlugin {
163
163
  return JSON.parse(buffer)
164
164
  }
165
165
  } catch (e) {
166
- log.error(e)
166
+ log.error('Sqs error parsing DD attributes', e)
167
167
  }
168
168
  }
169
169
 
@@ -0,0 +1,92 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('crypto')
4
+ const log = require('../../dd-trace/src/log')
5
+
6
+ /**
7
+ * Generates a unique hash from an array of strings by joining them with | before hashing.
8
+ * Used to uniquely identify AWS requests for span pointers.
9
+ * @param {string[]} components - Array of strings to hash
10
+ * @returns {string} A 32-character hash uniquely identifying the components
11
+ */
12
+ function generatePointerHash (components) {
13
+ // If passing S3's ETag as a component, make sure any quotes have already been removed!
14
+ const dataToHash = components.join('|')
15
+ const hash = crypto.createHash('sha256').update(dataToHash).digest('hex')
16
+ return hash.substring(0, 32)
17
+ }
18
+
19
+ /**
20
+ * Encodes a DynamoDB attribute value to Buffer for span pointer hashing.
21
+ * @param {Object} valueObject - DynamoDB value in AWS format ({ S: string } or { N: string } or { B: Buffer })
22
+ * @returns {Buffer|undefined} Encoded value as Buffer, or undefined if invalid input.
23
+ *
24
+ * @example
25
+ * encodeValue({ S: "user123" }) -> Buffer("user123")
26
+ * encodeValue({ N: "42" }) -> Buffer("42")
27
+ * encodeValue({ B: Buffer([1, 2, 3]) }) -> Buffer([1, 2, 3])
28
+ */
29
+ function encodeValue (valueObject) {
30
+ if (!valueObject) {
31
+ return
32
+ }
33
+
34
+ try {
35
+ const type = Object.keys(valueObject)[0]
36
+ const value = valueObject[type]
37
+
38
+ switch (type) {
39
+ case 'S':
40
+ return Buffer.from(value)
41
+ case 'N':
42
+ return Buffer.from(value.toString())
43
+ case 'B':
44
+ return Buffer.isBuffer(value) ? value : Buffer.from(value)
45
+ default:
46
+ log.debug(`Found unknown type while trying to create DynamoDB span pointer: ${type}`)
47
+ }
48
+ } catch (err) {
49
+ log.debug(`Failed to encode value while trying to create DynamoDB span pointer: ${err.message}`)
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Extracts and encodes primary key values from a DynamoDB item.
55
+ * Handles tables with single-key and two-key scenarios.
56
+ *
57
+ * @param {Set<string>} keySet - Set of primary key names.
58
+ * @param {Object} keyValuePairs - Object containing key/value pairs.
59
+ * @returns {Array|undefined} [key1Name, key1Value, key2Name, key2Value], or undefined if invalid input.
60
+ * key2 entries are empty strings in the single-key case.
61
+ * @example
62
+ * extractPrimaryKeys(new Set(['userId']), {userId: {S: "user123"}})
63
+ * // Returns ["userId", Buffer("user123"), "", ""]
64
+ * extractPrimaryKeys(new Set(['userId', 'timestamp']), {userId: {S: "user123"}, timestamp: {N: "1234}})
65
+ * // Returns ["timestamp", Buffer.from("1234"), "userId", Buffer.from("user123")]
66
+ */
67
+ const extractPrimaryKeys = (keySet, keyValuePairs) => {
68
+ const keyNames = Array.from(keySet)
69
+ if (keyNames.length === 0) {
70
+ return
71
+ }
72
+
73
+ if (keyNames.length === 1) {
74
+ const value = encodeValue(keyValuePairs[keyNames[0]])
75
+ if (value) {
76
+ return [keyNames[0], value, '', '']
77
+ }
78
+ } else {
79
+ const [key1, key2] = keyNames.sort()
80
+ const value1 = encodeValue(keyValuePairs[key1])
81
+ const value2 = encodeValue(keyValuePairs[key2])
82
+ if (value1 && value2) {
83
+ return [key1, value1, key2, value2]
84
+ }
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ generatePointerHash,
90
+ encodeValue,
91
+ extractPrimaryKeys
92
+ }
@@ -20,7 +20,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
20
20
  static get kind () { return 'server' }
21
21
  static get type () { return 'serverless' }
22
22
 
23
- static get prefix () { return 'tracing:datadog:azure-functions:invoke' }
23
+ static get prefix () { return 'tracing:datadog:azure:functions:invoke' }
24
24
 
25
25
  bindStart (ctx) {
26
26
  const { functionName, methodName } = ctx
@@ -6,7 +6,7 @@ const ALLOWED_ENV_VARIABLES = ['LD_PRELOAD', 'LD_LIBRARY_PATH', 'PATH']
6
6
  const PROCESS_DENYLIST = ['md5']
7
7
 
8
8
  const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
9
- // eslint-disable-next-line max-len
9
+ // eslint-disable-next-line @stylistic/js/max-len
10
10
  const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|address|api[-_]?key|e?mail|secret(?:[-_]?key)?|a(?:ccess|uth)[-_]?token|mysql_pwd|credentials|(?:stripe)?token)$'
11
11
  const regexParam = new RegExp(PARAM_PATTERN, 'i')
12
12
  const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
@@ -26,7 +26,12 @@ const {
26
26
  TEST_MODULE,
27
27
  TEST_MODULE_ID,
28
28
  TEST_SUITE,
29
- CUCUMBER_IS_PARALLEL
29
+ CUCUMBER_IS_PARALLEL,
30
+ TEST_NAME,
31
+ DI_ERROR_DEBUG_INFO_CAPTURED,
32
+ DI_DEBUG_ERROR_SNAPSHOT_ID,
33
+ DI_DEBUG_ERROR_FILE,
34
+ DI_DEBUG_ERROR_LINE
30
35
  } = require('../../dd-trace/src/plugins/util/test')
31
36
  const { RESOURCE_NAME } = require('../../../ext/tags')
32
37
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -46,6 +51,7 @@ const {
46
51
  const id = require('../../dd-trace/src/id')
47
52
 
48
53
  const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
54
+ const debuggerParameterPerTest = new Map()
49
55
 
50
56
  function getTestSuiteTags (testSuiteSpan) {
51
57
  const suiteTags = {
@@ -220,14 +226,40 @@ class CucumberPlugin extends CiPlugin {
220
226
  const testSpan = this.startTestSpan(testName, testSuite, extraTags)
221
227
 
222
228
  this.enter(testSpan, store)
229
+
230
+ const debuggerParameters = debuggerParameterPerTest.get(testName)
231
+
232
+ if (debuggerParameters) {
233
+ const spanContext = testSpan.context()
234
+
235
+ // TODO: handle race conditions with this.retriedTestIds
236
+ this.retriedTestIds = {
237
+ spanId: spanContext.toSpanId(),
238
+ traceId: spanContext.toTraceId()
239
+ }
240
+ const { snapshotId, file, line } = debuggerParameters
241
+
242
+ // TODO: should these be added on test:end if and only if the probe is hit?
243
+ // Sync issues: `hitProbePromise` might be resolved after the test ends
244
+ testSpan.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
245
+ testSpan.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
246
+ testSpan.setTag(DI_DEBUG_ERROR_FILE, file)
247
+ testSpan.setTag(DI_DEBUG_ERROR_LINE, line)
248
+ }
223
249
  })
224
250
 
225
- this.addSub('ci:cucumber:test:retry', (isFlakyRetry) => {
251
+ this.addSub('ci:cucumber:test:retry', ({ isRetry, error }) => {
226
252
  const store = storage.getStore()
227
253
  const span = store.span
228
- if (isFlakyRetry) {
254
+ if (isRetry) {
229
255
  span.setTag(TEST_IS_RETRY, 'true')
230
256
  }
257
+ span.setTag('error', error)
258
+ if (this.di && error && this.libraryConfig?.isDiEnabled) {
259
+ const testName = span.context()._tags[TEST_NAME]
260
+ const debuggerParameters = this.addDiProbe(error)
261
+ debuggerParameterPerTest.set(testName, debuggerParameters)
262
+ }
231
263
  span.setTag(TEST_STATUS, 'fail')
232
264
  span.finish()
233
265
  finishAllTraceSpans(span)
@@ -281,6 +313,7 @@ class CucumberPlugin extends CiPlugin {
281
313
  isStep,
282
314
  status,
283
315
  skipReason,
316
+ error,
284
317
  errorMessage,
285
318
  isNew,
286
319
  isEfdRetry,
@@ -302,7 +335,9 @@ class CucumberPlugin extends CiPlugin {
302
335
  span.setTag(TEST_SKIP_REASON, skipReason)
303
336
  }
304
337
 
305
- if (errorMessage) {
338
+ if (error) {
339
+ span.setTag('error', error)
340
+ } else if (errorMessage) { // we can't get a full error in cucumber steps
306
341
  span.setTag(ERROR_MESSAGE, errorMessage)
307
342
  }
308
343
 
@@ -223,7 +223,7 @@ class CypressPlugin {
223
223
  this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
224
224
  .then((libraryConfigurationResponse) => {
225
225
  if (libraryConfigurationResponse.err) {
226
- log.error(libraryConfigurationResponse.err)
226
+ log.error('Cypress plugin library config response error', libraryConfigurationResponse.err)
227
227
  } else {
228
228
  const {
229
229
  libraryConfig: {
@@ -360,7 +360,7 @@ class CypressPlugin {
360
360
  this.testConfiguration
361
361
  )
362
362
  if (knownTestsResponse.err) {
363
- log.error(knownTestsResponse.err)
363
+ log.error('Cypress known tests response error', knownTestsResponse.err)
364
364
  this.isEarlyFlakeDetectionEnabled = false
365
365
  } else {
366
366
  // We use TEST_FRAMEWORK_NAME for the name of the module
@@ -374,7 +374,7 @@ class CypressPlugin {
374
374
  this.testConfiguration
375
375
  )
376
376
  if (skippableTestsResponse.err) {
377
- log.error(skippableTestsResponse.err)
377
+ log.error('Cypress skippable tests response error', skippableTestsResponse.err)
378
378
  } else {
379
379
  const { skippableTests, correlationId } = skippableTestsResponse
380
380
  this.testsToSkip = skippableTests || []
@@ -62,7 +62,7 @@ class GrpcClientPlugin extends ClientPlugin {
62
62
  return parentStore
63
63
  }
64
64
 
65
- error ({ span, error }) {
65
+ error ({ span = this.activeSpan, error }) {
66
66
  this.addCode(span, error.code)
67
67
  if (error.code && !this._tracerConfig.grpc.client.error.statuses.includes(error.code)) {
68
68
  return
@@ -108,7 +108,7 @@ class GrpcClientPlugin extends ClientPlugin {
108
108
  }
109
109
 
110
110
  addCode (span, code) {
111
- if (code !== undefined) {
111
+ if (code !== undefined && span) {
112
112
  span.setTag('grpc.status.code', code)
113
113
  }
114
114
  }
@@ -54,7 +54,7 @@ module.exports = {
54
54
  }
55
55
 
56
56
  if (config.hasOwnProperty(filter)) {
57
- log.error(`Expected '${filter}' to be an array or function.`)
57
+ log.error('Expected \'%s\' to be an array or function.', filter)
58
58
  }
59
59
 
60
60
  return () => ({})
@@ -22,7 +22,12 @@ const {
22
22
  TEST_EARLY_FLAKE_ABORT_REASON,
23
23
  JEST_DISPLAY_NAME,
24
24
  TEST_IS_RUM_ACTIVE,
25
- TEST_BROWSER_DRIVER
25
+ TEST_BROWSER_DRIVER,
26
+ DI_ERROR_DEBUG_INFO_CAPTURED,
27
+ DI_DEBUG_ERROR_SNAPSHOT_ID,
28
+ DI_DEBUG_ERROR_FILE,
29
+ DI_DEBUG_ERROR_LINE,
30
+ TEST_NAME
26
31
  } = require('../../dd-trace/src/plugins/util/test')
27
32
  const { COMPONENT } = require('../../dd-trace/src/constants')
28
33
  const id = require('../../dd-trace/src/id')
@@ -39,6 +44,7 @@ const {
39
44
  } = require('../../dd-trace/src/ci-visibility/telemetry')
40
45
 
41
46
  const isJestWorker = !!process.env.JEST_WORKER_ID
47
+ const debuggerParameterPerTest = new Map()
42
48
 
43
49
  // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
44
50
  const CHILD_MESSAGE_END = 2
@@ -155,6 +161,7 @@ class JestPlugin extends CiPlugin {
155
161
  config._ddRepositoryRoot = this.repositoryRoot
156
162
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
157
163
  config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
164
+ config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
158
165
  })
159
166
  })
160
167
 
@@ -301,6 +308,29 @@ class JestPlugin extends CiPlugin {
301
308
  const span = this.startTestSpan(test)
302
309
 
303
310
  this.enter(span, store)
311
+
312
+ const { name: testName } = test
313
+
314
+ const debuggerParameters = debuggerParameterPerTest.get(testName)
315
+
316
+ // If we have a debugger probe, we need to add the snapshot id to the span
317
+ if (debuggerParameters) {
318
+ const spanContext = span.context()
319
+
320
+ // TODO: handle race conditions with this.retriedTestIds
321
+ this.retriedTestIds = {
322
+ spanId: spanContext.toSpanId(),
323
+ traceId: spanContext.toTraceId()
324
+ }
325
+ const { snapshotId, file, line } = debuggerParameters
326
+
327
+ // TODO: should these be added on test:end if and only if the probe is hit?
328
+ // Sync issues: `hitProbePromise` might be resolved after the test ends
329
+ span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
330
+ span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
331
+ span.setTag(DI_DEBUG_ERROR_FILE, file)
332
+ span.setTag(DI_DEBUG_ERROR_LINE, line)
333
+ }
304
334
  })
305
335
 
306
336
  this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
@@ -326,13 +356,19 @@ class JestPlugin extends CiPlugin {
326
356
  finishAllTraceSpans(span)
327
357
  })
328
358
 
329
- this.addSub('ci:jest:test:err', (error) => {
359
+ this.addSub('ci:jest:test:err', ({ error, willBeRetried, probe, isDiEnabled }) => {
330
360
  if (error) {
331
361
  const store = storage.getStore()
332
362
  if (store && store.span) {
333
363
  const span = store.span
334
364
  span.setTag(TEST_STATUS, 'fail')
335
365
  span.setTag('error', error)
366
+ if (willBeRetried && this.di && isDiEnabled) {
367
+ // if we use numTestExecutions, we have to remove the breakpoint after each execution
368
+ const testName = span.context()._tags[TEST_NAME]
369
+ const debuggerParameters = this.addDiProbe(error, probe)
370
+ debuggerParameterPerTest.set(testName, debuggerParameters)
371
+ }
336
372
  }
337
373
  }
338
374
  })
@@ -348,7 +384,6 @@ class JestPlugin extends CiPlugin {
348
384
  const {
349
385
  suite,
350
386
  name,
351
- runner,
352
387
  displayName,
353
388
  testParameters,
354
389
  frameworkVersion,
@@ -360,7 +395,7 @@ class JestPlugin extends CiPlugin {
360
395
  } = test
361
396
 
362
397
  const extraTags = {
363
- [JEST_TEST_RUNNER]: runner,
398
+ [JEST_TEST_RUNNER]: 'jest-circus',
364
399
  [TEST_PARAMETERS]: testParameters,
365
400
  [TEST_FRAMEWORK_VERSION]: frameworkVersion
366
401
  }
@@ -46,7 +46,7 @@ class LangChainChatModelHandler extends LangChainLanguageModelHandler {
46
46
 
47
47
  this.extractTokenMetrics(ctx.currentStore?.span, result)
48
48
 
49
- for (const messageSetIdx in result.generations) {
49
+ for (const messageSetIdx in result?.generations) {
50
50
  const messageSet = result.generations[messageSetIdx]
51
51
 
52
52
  for (const chatCompletionIdx in messageSet) {