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.
- package/LICENSE-3rdparty.csv +8 -2
- package/ci/init.js +16 -0
- package/index.d.ts +31 -13
- package/init.js +4 -68
- package/loader-hook.mjs +4 -0
- package/package.json +16 -11
- package/packages/datadog-core/src/storage.js +39 -2
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +29 -3
- package/packages/datadog-instrumentations/src/express.js +38 -4
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
- package/packages/datadog-instrumentations/src/http/client.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +27 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
- package/packages/datadog-instrumentations/src/mysql2.js +13 -8
- package/packages/datadog-instrumentations/src/next.js +7 -4
- package/packages/datadog-instrumentations/src/passport-http.js +2 -14
- package/packages/datadog-instrumentations/src/passport-local.js +2 -14
- package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
- package/packages/datadog-instrumentations/src/pg.js +6 -6
- package/packages/datadog-instrumentations/src/playwright.js +17 -4
- package/packages/datadog-instrumentations/src/router.js +97 -1
- package/packages/datadog-instrumentations/src/sequelize.js +9 -4
- package/packages/datadog-instrumentations/src/url.js +4 -0
- package/packages/datadog-instrumentations/src/vitest.js +27 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +39 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
- package/packages/datadog-plugin-grpc/src/client.js +2 -2
- package/packages/datadog-plugin-grpc/src/util.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +39 -4
- package/packages/datadog-plugin-mocha/src/index.js +36 -2
- package/packages/datadog-plugin-oracledb/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +34 -2
- package/packages/datadog-shimmer/src/shimmer.js +8 -4
- package/packages/dd-trace/src/appsec/addresses.js +3 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
- package/packages/dd-trace/src/appsec/index.js +16 -10
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
- package/packages/dd-trace/src/appsec/reporter.js +3 -1
- package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
- package/packages/dd-trace/src/appsec/telemetry.js +10 -0
- package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
- package/packages/dd-trace/src/azure_metadata.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/config.js +24 -32
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
- package/packages/dd-trace/src/datastreams/processor.js +4 -6
- package/packages/dd-trace/src/datastreams/writer.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
- package/packages/dd-trace/src/debugger/index.js +14 -10
- package/packages/dd-trace/src/dogstatsd.js +2 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -78
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
- package/packages/dd-trace/src/encode/span-stats.js +0 -30
- package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
- package/packages/dd-trace/src/flare/index.js +1 -1
- package/packages/dd-trace/src/guardrails/index.js +64 -0
- package/packages/dd-trace/src/guardrails/log.js +32 -0
- package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
- package/packages/dd-trace/src/guardrails/util.js +10 -0
- package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
- package/packages/dd-trace/src/llmobs/storage.js +2 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
- package/packages/dd-trace/src/log/channels.js +9 -2
- package/packages/dd-trace/src/log/index.js +11 -1
- package/packages/dd-trace/src/log/writer.js +14 -3
- package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
- package/packages/dd-trace/src/msgpack/encoder.js +309 -0
- package/packages/dd-trace/src/msgpack/index.js +6 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/git.js +7 -7
- package/packages/dd-trace/src/plugins/util/test.js +36 -3
- package/packages/dd-trace/src/plugins/util/web.js +2 -2
- package/packages/dd-trace/src/priority_sampler.js +11 -1
- package/packages/dd-trace/src/profiling/config.js +3 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
- package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
- package/packages/dd-trace/src/profiling/profiler.js +62 -10
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
- package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
- package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
- package/packages/dd-trace/src/proxy.js +7 -2
- package/packages/dd-trace/src/runtime_metrics.js +107 -4
- package/packages/dd-trace/src/serverless.js +1 -1
- package/packages/dd-trace/src/span_processor.js +10 -10
- package/packages/dd-trace/src/tagger.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +1 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +5 -16
- package/packages/datadog-instrumentations/src/qs.js +0 -24
- package/packages/dd-trace/src/appsec/passport.js +0 -110
- 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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
@@ -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('
|
|
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 {
|
|
@@ -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', (
|
|
251
|
+
this.addSub('ci:cucumber:test:retry', ({ isRetry, error }) => {
|
|
226
252
|
const store = storage.getStore()
|
|
227
253
|
const span = store.span
|
|
228
|
-
if (
|
|
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 (
|
|
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
|
}
|
|
@@ -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]:
|
|
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)
|