dd-trace 4.52.0 → 4.53.1

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 (148) 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 -68
  5. package/loader-hook.mjs +4 -0
  6. package/package.json +16 -11
  7. package/packages/datadog-core/src/storage.js +39 -2
  8. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +29 -3
  10. package/packages/datadog-instrumentations/src/express.js +38 -4
  11. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
  13. package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
  14. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +27 -8
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +13 -8
  18. package/packages/datadog-instrumentations/src/next.js +7 -4
  19. package/packages/datadog-instrumentations/src/passport-http.js +2 -14
  20. package/packages/datadog-instrumentations/src/passport-local.js +2 -14
  21. package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
  22. package/packages/datadog-instrumentations/src/pg.js +6 -6
  23. package/packages/datadog-instrumentations/src/playwright.js +17 -4
  24. package/packages/datadog-instrumentations/src/router.js +97 -1
  25. package/packages/datadog-instrumentations/src/sequelize.js +9 -4
  26. package/packages/datadog-instrumentations/src/url.js +4 -0
  27. package/packages/datadog-instrumentations/src/vitest.js +27 -2
  28. package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
  29. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  31. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  32. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  35. package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
  36. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  37. package/packages/datadog-plugin-cucumber/src/index.js +39 -4
  38. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
  39. package/packages/datadog-plugin-grpc/src/client.js +2 -2
  40. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  41. package/packages/datadog-plugin-jest/src/index.js +39 -4
  42. package/packages/datadog-plugin-mocha/src/index.js +36 -2
  43. package/packages/datadog-plugin-oracledb/src/index.js +1 -1
  44. package/packages/datadog-plugin-vitest/src/index.js +34 -2
  45. package/packages/datadog-shimmer/src/shimmer.js +8 -4
  46. package/packages/dd-trace/src/appsec/addresses.js +3 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
  48. package/packages/dd-trace/src/appsec/channels.js +1 -0
  49. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
  50. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
  52. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
  54. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
  56. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  58. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
  63. package/packages/dd-trace/src/appsec/index.js +16 -10
  64. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  65. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
  66. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  67. package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
  68. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  69. package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
  70. package/packages/dd-trace/src/azure_metadata.js +4 -4
  71. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
  73. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  74. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
  78. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  79. package/packages/dd-trace/src/config.js +24 -32
  80. package/packages/dd-trace/src/constants.js +1 -0
  81. package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
  82. package/packages/dd-trace/src/datastreams/processor.js +4 -6
  83. package/packages/dd-trace/src/datastreams/writer.js +6 -5
  84. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
  85. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
  88. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
  89. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
  91. package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
  92. package/packages/dd-trace/src/debugger/index.js +14 -10
  93. package/packages/dd-trace/src/dogstatsd.js +2 -2
  94. package/packages/dd-trace/src/encode/0.4.js +23 -78
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
  96. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
  97. package/packages/dd-trace/src/encode/span-stats.js +0 -30
  98. package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
  99. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  100. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
  101. package/packages/dd-trace/src/flare/index.js +1 -1
  102. package/packages/dd-trace/src/guardrails/index.js +64 -0
  103. package/packages/dd-trace/src/guardrails/log.js +32 -0
  104. package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
  105. package/packages/dd-trace/src/guardrails/util.js +10 -0
  106. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  107. package/packages/dd-trace/src/llmobs/storage.js +2 -3
  108. package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
  109. package/packages/dd-trace/src/log/channels.js +9 -2
  110. package/packages/dd-trace/src/log/index.js +11 -1
  111. package/packages/dd-trace/src/log/writer.js +14 -3
  112. package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
  113. package/packages/dd-trace/src/msgpack/encoder.js +309 -0
  114. package/packages/dd-trace/src/msgpack/index.js +6 -0
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
  117. package/packages/dd-trace/src/opentracing/span.js +1 -1
  118. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  119. package/packages/dd-trace/src/plugin_manager.js +4 -2
  120. package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
  121. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  122. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  123. package/packages/dd-trace/src/plugins/util/git.js +7 -7
  124. package/packages/dd-trace/src/plugins/util/test.js +36 -3
  125. package/packages/dd-trace/src/plugins/util/web.js +2 -2
  126. package/packages/dd-trace/src/priority_sampler.js +11 -1
  127. package/packages/dd-trace/src/profiling/config.js +3 -0
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
  129. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
  130. package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
  131. package/packages/dd-trace/src/profiling/profiler.js +62 -10
  132. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
  133. package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
  134. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
  135. package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
  136. package/packages/dd-trace/src/proxy.js +7 -2
  137. package/packages/dd-trace/src/runtime_metrics.js +107 -4
  138. package/packages/dd-trace/src/serverless.js +1 -1
  139. package/packages/dd-trace/src/span_processor.js +10 -10
  140. package/packages/dd-trace/src/tagger.js +1 -1
  141. package/packages/dd-trace/src/telemetry/index.js +1 -0
  142. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
  144. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  145. package/packages/dd-trace/src/util.js +5 -16
  146. package/packages/datadog-instrumentations/src/qs.js +0 -24
  147. package/packages/dd-trace/src/appsec/passport.js +0 -110
  148. package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
@@ -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
+ }
@@ -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
  }
@@ -30,7 +30,12 @@ const {
30
30
  TEST_SUITE,
31
31
  MOCHA_IS_PARALLEL,
32
32
  TEST_IS_RUM_ACTIVE,
33
- TEST_BROWSER_DRIVER
33
+ TEST_BROWSER_DRIVER,
34
+ TEST_NAME,
35
+ DI_ERROR_DEBUG_INFO_CAPTURED,
36
+ DI_DEBUG_ERROR_SNAPSHOT_ID,
37
+ DI_DEBUG_ERROR_FILE,
38
+ DI_DEBUG_ERROR_LINE
34
39
  } = require('../../dd-trace/src/plugins/util/test')
35
40
  const { COMPONENT } = require('../../dd-trace/src/constants')
36
41
  const {
@@ -47,6 +52,8 @@ const {
47
52
  const id = require('../../dd-trace/src/id')
48
53
  const log = require('../../dd-trace/src/log')
49
54
 
55
+ const debuggerParameterPerTest = new Map()
56
+
50
57
  function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
51
58
  const testSuiteSpanContext = testSuiteSpan.context()
52
59
  const suiteTags = {
@@ -185,6 +192,28 @@ class MochaPlugin extends CiPlugin {
185
192
  const store = storage.getStore()
186
193
  const span = this.startTestSpan(testInfo)
187
194
 
195
+ const { testName } = testInfo
196
+
197
+ const debuggerParameters = debuggerParameterPerTest.get(testName)
198
+
199
+ if (debuggerParameters) {
200
+ const spanContext = span.context()
201
+
202
+ // TODO: handle race conditions with this.retriedTestIds
203
+ this.retriedTestIds = {
204
+ spanId: spanContext.toSpanId(),
205
+ traceId: spanContext.toTraceId()
206
+ }
207
+ const { snapshotId, file, line } = debuggerParameters
208
+
209
+ // TODO: should these be added on test:end if and only if the probe is hit?
210
+ // Sync issues: `hitProbePromise` might be resolved after the test ends
211
+ span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
212
+ span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
213
+ span.setTag(DI_DEBUG_ERROR_FILE, file)
214
+ span.setTag(DI_DEBUG_ERROR_LINE, line)
215
+ }
216
+
188
217
  this.enter(span, store)
189
218
  })
190
219
 
@@ -242,7 +271,7 @@ class MochaPlugin extends CiPlugin {
242
271
  }
243
272
  })
244
273
 
245
- this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, err }) => {
274
+ this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err }) => {
246
275
  const store = storage.getStore()
247
276
  const span = store?.span
248
277
  if (span) {
@@ -265,6 +294,11 @@ class MochaPlugin extends CiPlugin {
265
294
  browserDriver: spanTags[TEST_BROWSER_DRIVER]
266
295
  }
267
296
  )
297
+ if (willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
298
+ const testName = span.context()._tags[TEST_NAME]
299
+ const debuggerParameters = this.addDiProbe(err)
300
+ debuggerParameterPerTest.set(testName, debuggerParameters)
301
+ }
268
302
 
269
303
  span.finish()
270
304
  finishAllTraceSpans(span)
@@ -33,7 +33,7 @@ function getUrl (connectString) {
33
33
  try {
34
34
  return new URL(`http://${connectString}`)
35
35
  } catch (e) {
36
- log.error(e)
36
+ log.error('Invalid oracle connection string', e)
37
37
  return {}
38
38
  }
39
39
  }