dd-trace 5.11.0 → 5.13.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 (40) hide show
  1. package/ci/init.js +7 -0
  2. package/ext/exporters.d.ts +2 -1
  3. package/ext/exporters.js +2 -1
  4. package/index.d.ts +14 -6
  5. package/package.json +4 -4
  6. package/packages/datadog-esbuild/index.js +8 -2
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +182 -105
  9. package/packages/datadog-instrumentations/src/helpers/fetch.js +9 -4
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  11. package/packages/datadog-instrumentations/src/lodash.js +31 -0
  12. package/packages/datadog-instrumentations/src/openai.js +149 -0
  13. package/packages/datadog-instrumentations/src/playwright.js +6 -1
  14. package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
  18. package/packages/datadog-plugin-cucumber/src/index.js +83 -11
  19. package/packages/datadog-plugin-fetch/src/index.js +5 -2
  20. package/packages/datadog-plugin-openai/src/index.js +159 -32
  21. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +1 -9
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -2
  27. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
  28. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
  29. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
  31. package/packages/dd-trace/src/appsec/remote_config/index.js +5 -5
  32. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
  33. package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
  34. package/packages/dd-trace/src/config.js +7 -0
  35. package/packages/dd-trace/src/exporter.js +2 -1
  36. package/packages/dd-trace/src/plugins/database.js +20 -5
  37. package/packages/dd-trace/src/plugins/util/test.js +7 -0
  38. package/packages/dd-trace/src/proxy.js +26 -6
  39. package/packages/dd-trace/src/telemetry/index.js +9 -3
  40. package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
@@ -10,6 +10,98 @@ const startCh = channel('apm:openai:request:start')
10
10
  const finishCh = channel('apm:openai:request:finish')
11
11
  const errorCh = channel('apm:openai:request:error')
12
12
 
13
+ const V4_PACKAGE_SHIMS = [
14
+ {
15
+ file: 'resources/chat/completions.js',
16
+ targetClass: 'Completions',
17
+ baseResource: 'chat.completions',
18
+ methods: ['create']
19
+ },
20
+ {
21
+ file: 'resources/completions.js',
22
+ targetClass: 'Completions',
23
+ baseResource: 'completions',
24
+ methods: ['create']
25
+ },
26
+ {
27
+ file: 'resources/embeddings.js',
28
+ targetClass: 'Embeddings',
29
+ baseResource: 'embeddings',
30
+ methods: ['create']
31
+ },
32
+ {
33
+ file: 'resources/files.js',
34
+ targetClass: 'Files',
35
+ baseResource: 'files',
36
+ methods: ['create', 'del', 'list', 'retrieve']
37
+ },
38
+ {
39
+ file: 'resources/files.js',
40
+ targetClass: 'Files',
41
+ baseResource: 'files',
42
+ methods: ['retrieveContent'],
43
+ versions: ['>=4.0.0 <4.17.1']
44
+ },
45
+ {
46
+ file: 'resources/files.js',
47
+ targetClass: 'Files',
48
+ baseResource: 'files',
49
+ methods: ['content'], // replaced `retrieveContent` in v4.17.1
50
+ versions: ['>=4.17.1']
51
+ },
52
+ {
53
+ file: 'resources/images.js',
54
+ targetClass: 'Images',
55
+ baseResource: 'images',
56
+ methods: ['createVariation', 'edit', 'generate']
57
+ },
58
+ {
59
+ file: 'resources/fine-tuning/jobs/jobs.js',
60
+ targetClass: 'Jobs',
61
+ baseResource: 'fine_tuning.jobs',
62
+ methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
63
+ versions: ['>=4.34.0'] // file location changed in 4.34.0
64
+ },
65
+ {
66
+ file: 'resources/fine-tuning/jobs.js',
67
+ targetClass: 'Jobs',
68
+ baseResource: 'fine_tuning.jobs',
69
+ methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
70
+ versions: ['>=4.1.0 <4.34.0']
71
+ },
72
+ {
73
+ file: 'resources/fine-tunes.js', // deprecated after 4.1.0
74
+ targetClass: 'FineTunes',
75
+ baseResource: 'fine-tune',
76
+ methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
77
+ versions: ['>=4.0.0 <4.1.0']
78
+ },
79
+ {
80
+ file: 'resources/models.js',
81
+ targetClass: 'Models',
82
+ baseResource: 'models',
83
+ methods: ['del', 'list', 'retrieve']
84
+ },
85
+ {
86
+ file: 'resources/moderations.js',
87
+ targetClass: 'Moderations',
88
+ baseResource: 'moderations',
89
+ methods: ['create']
90
+ },
91
+ {
92
+ file: 'resources/audio/transcriptions.js',
93
+ targetClass: 'Transcriptions',
94
+ baseResource: 'audio.transcriptions',
95
+ methods: ['create']
96
+ },
97
+ {
98
+ file: 'resources/audio/translations.js',
99
+ targetClass: 'Translations',
100
+ baseResource: 'audio.translations',
101
+ methods: ['create']
102
+ }
103
+ ]
104
+
13
105
  addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, exports => {
14
106
  const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
15
107
  methodNames.shift() // remove leading 'constructor' method
@@ -48,3 +140,60 @@ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, expor
48
140
 
49
141
  return exports
50
142
  })
143
+
144
+ for (const shim of V4_PACKAGE_SHIMS) {
145
+ const { file, targetClass, baseResource, methods } = shim
146
+ addHook({ name: 'openai', file, versions: shim.versions || ['>=4'] }, exports => {
147
+ const targetPrototype = exports[targetClass].prototype
148
+
149
+ for (const methodName of methods) {
150
+ shimmer.wrap(targetPrototype, methodName, methodFn => function () {
151
+ if (!startCh.hasSubscribers) {
152
+ return methodFn.apply(this, arguments)
153
+ }
154
+
155
+ const client = this._client || this.client
156
+
157
+ startCh.publish({
158
+ methodName: `${baseResource}.${methodName}`,
159
+ args: arguments,
160
+ basePath: client.baseURL,
161
+ apiKey: client.apiKey
162
+ })
163
+
164
+ const apiProm = methodFn.apply(this, arguments)
165
+
166
+ // wrapping `parse` avoids problematic wrapping of `then` when trying to call
167
+ // `withResponse` in userland code after. This way, we can return the whole `APIPromise`
168
+ shimmer.wrap(apiProm, 'parse', origApiPromParse => function () {
169
+ return origApiPromParse.apply(this, arguments)
170
+ // the original response is wrapped in a promise, so we need to unwrap it
171
+ .then(body => Promise.all([this.responsePromise, body]))
172
+ .then(([{ response, options }, body]) => {
173
+ finishCh.publish({
174
+ headers: response.headers,
175
+ body,
176
+ path: response.url,
177
+ method: options.method
178
+ })
179
+
180
+ return body
181
+ })
182
+ .catch(err => {
183
+ errorCh.publish({ err })
184
+
185
+ throw err
186
+ })
187
+ .finally(() => {
188
+ // maybe we don't want to unwrap here in case the promise is re-used?
189
+ // other hand: we want to avoid resource leakage
190
+ shimmer.unwrap(apiProm, 'parse')
191
+ })
192
+ })
193
+
194
+ return apiProm
195
+ })
196
+ }
197
+ return exports
198
+ })
199
+ }
@@ -297,7 +297,12 @@ function dispatcherRunWrapper (run) {
297
297
  }
298
298
 
299
299
  function dispatcherRunWrapperNew (run) {
300
- return function () {
300
+ return function (testGroups) {
301
+ if (!this._allTests) {
302
+ // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
303
+ // Not available from >=1.44.0
304
+ this._allTests = testGroups.map(g => g.tests).flat()
305
+ }
301
306
  remainingTestsByFile = getTestsBySuiteFromTestGroups(arguments[0])
302
307
  return run.apply(this, arguments)
303
308
  }
@@ -7,6 +7,9 @@ exports.kinesis = require('./kinesis')
7
7
  exports.lambda = require('./lambda')
8
8
  exports.redshift = require('./redshift')
9
9
  exports.s3 = require('./s3')
10
+ exports.sfn = require('./sfn')
10
11
  exports.sns = require('./sns')
11
12
  exports.sqs = require('./sqs')
13
+ exports.states = require('./states')
14
+ exports.stepfunctions = require('./stepfunctions')
12
15
  exports.default = require('./default')
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+ const Stepfunctions = require('./stepfunctions')
3
+ class Sfn extends Stepfunctions {
4
+ static get id () { return 'sfn' }
5
+ }
6
+
7
+ module.exports = Sfn
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+ const Stepfunctions = require('./stepfunctions')
3
+ class States extends Stepfunctions {
4
+ static get id () { return 'states' }
5
+ }
6
+
7
+ module.exports = States
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+ const log = require('../../../dd-trace/src/log')
3
+ const BaseAwsSdkPlugin = require('../base')
4
+
5
+ class Stepfunctions extends BaseAwsSdkPlugin {
6
+ static get id () { return 'stepfunctions' }
7
+
8
+ // This is the shape of StartExecutionInput, as defined in
9
+ // https://github.com/aws/aws-sdk-js/blob/master/apis/states-2016-11-23.normal.json
10
+ // "StartExecutionInput": {
11
+ // "type": "structure",
12
+ // "required": [
13
+ // "stateMachineArn"
14
+ // ],
15
+ // "members": {
16
+ // "stateMachineArn": {
17
+ // "shape": "Arn",
18
+ // },
19
+ // "name": {
20
+ // "shape": "Name",
21
+ // },
22
+ // "input": {
23
+ // "shape": "SensitiveData",
24
+ // },
25
+ // "traceHeader": {
26
+ // "shape": "TraceHeader",
27
+ // }
28
+ // }
29
+
30
+ generateTags (params, operation, response) {
31
+ if (!params) return {}
32
+ const tags = { 'resource.name': params.name ? `${operation} ${params.name}` : `${operation}` }
33
+ if (operation === 'startExecution' || operation === 'startSyncExecution') {
34
+ tags.statemachinearn = `${params.stateMachineArn}`
35
+ }
36
+ return tags
37
+ }
38
+
39
+ requestInject (span, request) {
40
+ const operation = request.operation
41
+ if (operation === 'startExecution' || operation === 'startSyncExecution') {
42
+ if (!request.params || !request.params.input) {
43
+ return
44
+ }
45
+
46
+ const input = request.params.input
47
+
48
+ try {
49
+ const inputObj = JSON.parse(input)
50
+ if (inputObj && typeof inputObj === 'object') {
51
+ // We've parsed the input JSON string
52
+ inputObj._datadog = {}
53
+ this.tracer.inject(span, 'text_map', inputObj._datadog)
54
+ const newInput = JSON.stringify(inputObj)
55
+ request.params.input = newInput
56
+ }
57
+ } catch (e) {
58
+ log.info('Unable to treat input as JSON')
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ module.exports = Stepfunctions
@@ -18,7 +18,14 @@ const {
18
18
  TEST_SOURCE_FILE,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
20
  TEST_IS_NEW,
21
- TEST_IS_RETRY
21
+ TEST_IS_RETRY,
22
+ TEST_SUITE_ID,
23
+ TEST_SESSION_ID,
24
+ TEST_COMMAND,
25
+ TEST_MODULE,
26
+ TEST_MODULE_ID,
27
+ TEST_SUITE,
28
+ CUCUMBER_IS_PARALLEL
22
29
  } = require('../../dd-trace/src/plugins/util/test')
23
30
  const { RESOURCE_NAME } = require('../../../ext/tags')
24
31
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -32,6 +39,22 @@ const {
32
39
  TELEMETRY_ITR_UNSKIPPABLE,
33
40
  TELEMETRY_CODE_COVERAGE_NUM_FILES
34
41
  } = require('../../dd-trace/src/ci-visibility/telemetry')
42
+ const id = require('../../dd-trace/src/id')
43
+
44
+ const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
45
+
46
+ function getTestSuiteTags (testSuiteSpan) {
47
+ const suiteTags = {
48
+ [TEST_SUITE_ID]: testSuiteSpan.context().toSpanId(),
49
+ [TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
50
+ [TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
51
+ [TEST_MODULE]: 'cucumber'
52
+ }
53
+ if (testSuiteSpan.context()._parentId) {
54
+ suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
55
+ }
56
+ return suiteTags
57
+ }
35
58
 
36
59
  class CucumberPlugin extends CiPlugin {
37
60
  static get id () {
@@ -43,6 +66,8 @@ class CucumberPlugin extends CiPlugin {
43
66
 
44
67
  this.sourceRoot = process.cwd()
45
68
 
69
+ this.testSuiteSpanByPath = {}
70
+
46
71
  this.addSub('ci:cucumber:session:finish', ({
47
72
  status,
48
73
  isSuitesSkipped,
@@ -50,7 +75,8 @@ class CucumberPlugin extends CiPlugin {
50
75
  testCodeCoverageLinesTotal,
51
76
  hasUnskippableSuites,
52
77
  hasForcedToRunSuites,
53
- isEarlyFlakeDetectionEnabled
78
+ isEarlyFlakeDetectionEnabled,
79
+ isParallel
54
80
  }) => {
55
81
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
56
82
  addIntelligentTestRunnerSpanTags(
@@ -70,6 +96,9 @@ class CucumberPlugin extends CiPlugin {
70
96
  if (isEarlyFlakeDetectionEnabled) {
71
97
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
72
98
  }
99
+ if (isParallel) {
100
+ this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
101
+ }
73
102
 
74
103
  this.testSessionSpan.setTag(TEST_STATUS, status)
75
104
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -101,7 +130,7 @@ class CucumberPlugin extends CiPlugin {
101
130
  if (itrCorrelationId) {
102
131
  testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
103
132
  }
104
- this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
133
+ const testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
105
134
  childOf: this.testModuleSpan,
106
135
  tags: {
107
136
  [COMPONENT]: this.constructor.id,
@@ -109,25 +138,29 @@ class CucumberPlugin extends CiPlugin {
109
138
  ...testSuiteMetadata
110
139
  }
111
140
  })
141
+ this.testSuiteSpanByPath[testSuitePath] = testSuiteSpan
142
+
112
143
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
113
144
  if (this.libraryConfig?.isCodeCoverageEnabled) {
114
145
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
115
146
  }
116
147
  })
117
148
 
118
- this.addSub('ci:cucumber:test-suite:finish', status => {
119
- this.testSuiteSpan.setTag(TEST_STATUS, status)
120
- this.testSuiteSpan.finish()
149
+ this.addSub('ci:cucumber:test-suite:finish', ({ status, testSuitePath }) => {
150
+ const testSuiteSpan = this.testSuiteSpanByPath[testSuitePath]
151
+ testSuiteSpan.setTag(TEST_STATUS, status)
152
+ testSuiteSpan.finish()
121
153
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
122
154
  })
123
155
 
124
- this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
156
+ this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile, testSuitePath }) => {
125
157
  if (!this.libraryConfig?.isCodeCoverageEnabled) {
126
158
  return
127
159
  }
128
160
  if (!coverageFiles.length) {
129
161
  this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
130
162
  }
163
+ const testSuiteSpan = this.testSuiteSpanByPath[testSuitePath]
131
164
 
132
165
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
133
166
  .map(filename => getTestSuitePath(filename, this.repositoryRoot))
@@ -135,8 +168,8 @@ class CucumberPlugin extends CiPlugin {
135
168
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
136
169
 
137
170
  const formattedCoverage = {
138
- sessionId: this.testSuiteSpan.context()._traceId,
139
- suiteId: this.testSuiteSpan.context()._spanId,
171
+ sessionId: testSuiteSpan.context()._traceId,
172
+ suiteId: testSuiteSpan.context()._spanId,
140
173
  files: relativeCoverageFiles
141
174
  }
142
175
 
@@ -144,7 +177,7 @@ class CucumberPlugin extends CiPlugin {
144
177
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
145
178
  })
146
179
 
147
- this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
180
+ this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine, isParallel }) => {
148
181
  const store = storage.getStore()
149
182
  const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
150
183
  const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
@@ -153,6 +186,10 @@ class CucumberPlugin extends CiPlugin {
153
186
  [TEST_SOURCE_START]: testSourceLine,
154
187
  [TEST_SOURCE_FILE]: testSourceFile
155
188
  }
189
+ if (isParallel) {
190
+ extraTags[CUCUMBER_IS_PARALLEL] = 'true'
191
+ }
192
+
156
193
  const testSpan = this.startTestSpan(testName, testSuite, extraTags)
157
194
 
158
195
  this.enter(testSpan, store)
@@ -172,6 +209,36 @@ class CucumberPlugin extends CiPlugin {
172
209
  this.enter(span, store)
173
210
  })
174
211
 
212
+ this.addSub('ci:cucumber:worker-report:trace', (traces) => {
213
+ const formattedTraces = JSON.parse(traces).map(trace =>
214
+ trace.map(span => ({
215
+ ...span,
216
+ span_id: id(span.span_id),
217
+ trace_id: id(span.trace_id),
218
+ parent_id: id(span.parent_id)
219
+ }))
220
+ )
221
+
222
+ // We have to update the test session, test module and test suite ids
223
+ // before we export them in the main process
224
+ formattedTraces.forEach(trace => {
225
+ trace.forEach(span => {
226
+ if (span.name === 'cucumber.test') {
227
+ const testSuite = span.meta[TEST_SUITE]
228
+ const testSuiteSpan = this.testSuiteSpanByPath[testSuite]
229
+
230
+ const testSuiteTags = getTestSuiteTags(testSuiteSpan)
231
+ span.meta = {
232
+ ...span.meta,
233
+ ...testSuiteTags
234
+ }
235
+ }
236
+ })
237
+
238
+ this.tracer._exporter.export(trace)
239
+ })
240
+ })
241
+
175
242
  this.addSub('ci:cucumber:test:finish', ({ isStep, status, skipReason, errorMessage, isNew, isEfdRetry }) => {
176
243
  const span = storage.getStore().span
177
244
  const statusTag = isStep ? 'step.status' : TEST_STATUS
@@ -201,6 +268,10 @@ class CucumberPlugin extends CiPlugin {
201
268
  { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
202
269
  )
203
270
  finishAllTraceSpans(span)
271
+ // If it's a worker, flushing is cheap, as it's just sending data to the main process
272
+ if (isCucumberWorker) {
273
+ this.tracer._exporter.flush()
274
+ }
204
275
  }
205
276
  })
206
277
 
@@ -213,10 +284,11 @@ class CucumberPlugin extends CiPlugin {
213
284
  }
214
285
 
215
286
  startTestSpan (testName, testSuite, extraTags) {
287
+ const testSuiteSpan = this.testSuiteSpanByPath[testSuite]
216
288
  return super.startTestSpan(
217
289
  testName,
218
290
  testSuite,
219
- this.testSuiteSpan,
291
+ testSuiteSpan,
220
292
  extraTags
221
293
  )
222
294
  }
@@ -17,8 +17,11 @@ class FetchPlugin extends HttpClientPlugin {
17
17
 
18
18
  const store = super.bindStart(ctx)
19
19
 
20
- ctx.headers = headers
21
- ctx.req = new globalThis.Request(req, { headers })
20
+ for (const name in headers) {
21
+ if (!req.headers.has(name)) {
22
+ req.headers.set(name, headers[name])
23
+ }
24
+ }
22
25
 
23
26
  return store
24
27
  }