dd-trace 5.10.0 → 5.12.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 (54) hide show
  1. package/index.d.ts +15 -0
  2. package/package.json +2 -1
  3. package/packages/datadog-instrumentations/src/fetch.js +6 -45
  4. package/packages/datadog-instrumentations/src/helpers/fetch.js +22 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
  6. package/packages/datadog-instrumentations/src/jest.js +77 -10
  7. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  8. package/packages/datadog-instrumentations/src/openai.js +149 -0
  9. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +6 -1
  10. package/packages/datadog-instrumentations/src/selenium.js +69 -0
  11. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  12. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  13. package/packages/datadog-plugin-cypress/src/support.js +19 -3
  14. package/packages/datadog-plugin-fetch/src/index.js +20 -11
  15. package/packages/datadog-plugin-jest/src/index.js +7 -2
  16. package/packages/datadog-plugin-mocha/src/index.js +4 -5
  17. package/packages/datadog-plugin-openai/src/index.js +159 -32
  18. package/packages/datadog-plugin-openai/src/services.js +2 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  20. package/packages/datadog-plugin-selenium/src/index.js +71 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +70 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +14 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +12 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-rule-type.js +6 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +5 -50
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +742 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +539 -66
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +1 -9
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -2
  31. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  32. package/packages/dd-trace/src/appsec/remote_config/index.js +5 -5
  33. package/packages/dd-trace/src/appsec/reporter.js +11 -10
  34. package/packages/dd-trace/src/appsec/telemetry.js +36 -7
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  36. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  37. package/packages/dd-trace/src/config.js +94 -9
  38. package/packages/dd-trace/src/dogstatsd.js +13 -11
  39. package/packages/dd-trace/src/index.js +5 -1
  40. package/packages/dd-trace/src/noop/dogstatsd.js +11 -0
  41. package/packages/dd-trace/src/noop/proxy.js +3 -0
  42. package/packages/dd-trace/src/opentracing/propagation/text_map.js +10 -4
  43. package/packages/dd-trace/src/opentracing/span.js +2 -0
  44. package/packages/dd-trace/src/plugins/index.js +2 -0
  45. package/packages/dd-trace/src/plugins/util/test.js +34 -3
  46. package/packages/dd-trace/src/profiling/config.js +8 -4
  47. package/packages/dd-trace/src/profiling/exporters/agent.js +5 -3
  48. package/packages/dd-trace/src/profiling/profiler.js +4 -0
  49. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +33 -0
  50. package/packages/dd-trace/src/profiling/ssi-telemetry.js +167 -0
  51. package/packages/dd-trace/src/proxy.js +33 -7
  52. package/packages/dd-trace/src/tagger.js +13 -3
  53. package/packages/dd-trace/src/telemetry/index.js +5 -4
  54. package/packages/dd-trace/src/telemetry/metrics.js +2 -2
package/index.d.ts CHANGED
@@ -187,6 +187,7 @@ interface Plugins {
187
187
  "restify": tracer.plugins.restify;
188
188
  "rhea": tracer.plugins.rhea;
189
189
  "router": tracer.plugins.router;
190
+ "selenium": tracer.plugins.selenium;
190
191
  "sharedb": tracer.plugins.sharedb;
191
192
  "tedious": tracer.plugins.tedious;
192
193
  "winston": tracer.plugins.winston;
@@ -789,6 +790,14 @@ declare namespace tracer {
789
790
  */
790
791
  gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
791
792
 
793
+ /**
794
+ * Sets a histogram value, optionally specifying tags.
795
+ * @param {string} stat The dot-separated metric name.
796
+ * @param {number} value The amount to increment the stat by.
797
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
798
+ */
799
+ histogram(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
800
+
792
801
  /**
793
802
  * Forces any unsent metrics to be sent
794
803
  *
@@ -1728,6 +1737,12 @@ declare namespace tracer {
1728
1737
  */
1729
1738
  interface router extends Integration {}
1730
1739
 
1740
+ /**
1741
+ * This plugin automatically instruments the
1742
+ * [selenium-webdriver](https://www.npmjs.com/package/selenium-webdriver) module.
1743
+ */
1744
+ interface selenium extends Integration {}
1745
+
1731
1746
  /**
1732
1747
  * This plugin automatically instruments the
1733
1748
  * [sharedb](https://github.com/share/sharedb) module.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.10.0",
3
+ "version": "5.12.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -36,6 +36,7 @@
36
36
  "test:integration:cucumber": "mocha --colors --timeout 30000 \"integration-tests/cucumber/*.spec.js\"",
37
37
  "test:integration:cypress": "mocha --colors --timeout 30000 \"integration-tests/cypress/*.spec.js\"",
38
38
  "test:integration:playwright": "mocha --colors --timeout 30000 \"integration-tests/playwright/*.spec.js\"",
39
+ "test:integration:selenium": "mocha --colors --timeout 30000 \"integration-tests/selenium/*.spec.js\"",
39
40
  "test:integration:profiler": "mocha --colors --timeout 90000 \"integration-tests/profiler/*.spec.js\"",
40
41
  "test:integration:serverless": "mocha --colors --timeout 30000 \"integration-tests/serverless/*.spec.js\"",
41
42
  "test:integration:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"",
@@ -1,51 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { channel } = require('./helpers/instrument')
5
-
6
- const startChannel = channel('apm:fetch:request:start')
7
- const finishChannel = channel('apm:fetch:request:finish')
8
- const errorChannel = channel('apm:fetch:request:error')
9
-
10
- function wrapFetch (fetch, Request) {
11
- if (typeof fetch !== 'function') return fetch
12
-
13
- return function (input, init) {
14
- if (!startChannel.hasSubscribers) return fetch.apply(this, arguments)
15
-
16
- const req = new Request(input, init)
17
- const headers = req.headers
18
- const message = { req, headers }
19
-
20
- return startChannel.runStores(message, () => {
21
- // Request object is read-only so we need new objects to change headers.
22
- arguments[0] = message.req
23
- arguments[1] = { headers: message.headers }
24
-
25
- return fetch.apply(this, arguments)
26
- .then(
27
- res => {
28
- message.res = res
29
-
30
- finishChannel.publish(message)
31
-
32
- return res
33
- },
34
- err => {
35
- if (err.name !== 'AbortError') {
36
- message.error = err
37
- errorChannel.publish(message)
38
- }
39
-
40
- finishChannel.publish(message)
41
-
42
- throw err
43
- }
44
- )
45
- })
46
- }
47
- }
4
+ const { tracingChannel } = require('dc-polyfill')
5
+ const { createWrapFetch } = require('./helpers/fetch')
48
6
 
49
7
  if (globalThis.fetch) {
50
- globalThis.fetch = shimmer.wrap(fetch, wrapFetch(fetch, globalThis.Request))
8
+ const ch = tracingChannel('apm:fetch:request')
9
+ const wrapFetch = createWrapFetch(globalThis.Request, ch)
10
+
11
+ globalThis.fetch = shimmer.wrap(fetch, wrapFetch(fetch))
51
12
  }
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ exports.createWrapFetch = function createWrapFetch (Request, ch) {
4
+ return function wrapFetch (fetch) {
5
+ if (typeof fetch !== 'function') return fetch
6
+
7
+ return function (input, init) {
8
+ if (!ch.start.hasSubscribers) return fetch.apply(this, arguments)
9
+
10
+ if (input instanceof Request) {
11
+ const ctx = { req: input }
12
+
13
+ return ch.tracePromise(() => fetch.call(this, input, init), ctx)
14
+ } else {
15
+ const req = new Request(input, init)
16
+ const ctx = { req }
17
+
18
+ return ch.tracePromise(() => fetch.call(this, req), ctx)
19
+ }
20
+ }
21
+ }
22
+ }
@@ -58,6 +58,7 @@ module.exports = {
58
58
  'jest-environment-node': () => require('../jest'),
59
59
  'jest-environment-jsdom': () => require('../jest'),
60
60
  'jest-jasmine2': () => require('../jest'),
61
+ 'jest-runtime': () => require('../jest'),
61
62
  'jest-worker': () => require('../jest'),
62
63
  knex: () => require('../knex'),
63
64
  koa: () => require('../koa'),
@@ -103,8 +104,9 @@ module.exports = {
103
104
  restify: () => require('../restify'),
104
105
  rhea: () => require('../rhea'),
105
106
  router: () => require('../router'),
106
- sharedb: () => require('../sharedb'),
107
+ 'selenium-webdriver': () => require('../selenium'),
107
108
  sequelize: () => require('../sequelize'),
109
+ sharedb: () => require('../sharedb'),
108
110
  tedious: () => require('../tedious'),
109
111
  when: () => require('../when'),
110
112
  winston: () => require('../winston')
@@ -11,7 +11,8 @@ const {
11
11
  getTestSuitePath,
12
12
  getTestParametersString,
13
13
  addEfdStringToTestName,
14
- removeEfdStringFromTestName
14
+ removeEfdStringFromTestName,
15
+ getIsFaultyEarlyFlakeDetection
15
16
  } = require('../../dd-trace/src/plugins/util/test')
16
17
  const {
17
18
  getFormattedJestTestParameters,
@@ -61,6 +62,8 @@ let hasUnskippableSuites = false
61
62
  let hasForcedToRunSuites = false
62
63
  let isEarlyFlakeDetectionEnabled = false
63
64
  let earlyFlakeDetectionNumRetries = 0
65
+ let earlyFlakeDetectionFaultyThreshold = 30
66
+ let isEarlyFlakeDetectionFaulty = false
64
67
  let hasFilteredSkippableSuites = false
65
68
 
66
69
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
@@ -121,6 +124,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
121
124
  this.testSuite = getTestSuitePath(context.testPath, rootDir)
122
125
  this.nameToParams = {}
123
126
  this.global._ddtrace = global._ddtrace
127
+ this.hasSnapshotTests = undefined
124
128
 
125
129
  this.displayName = config.projectConfig?.displayName?.name
126
130
  this.testEnvironmentOptions = getTestEnvironmentOptions(config)
@@ -147,6 +151,21 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
147
151
  }
148
152
  }
149
153
 
154
+ getHasSnapshotTests () {
155
+ if (this.hasSnapshotTests !== undefined) {
156
+ return this.hasSnapshotTests
157
+ }
158
+ let hasSnapshotTests = true
159
+ try {
160
+ const { _snapshotData } = this.context.expect.getState().snapshotState
161
+ hasSnapshotTests = Object.keys(_snapshotData).length > 0
162
+ } catch (e) {
163
+ // if we can't be sure, we'll err on the side of caution and assume it has snapshots
164
+ }
165
+ this.hasSnapshotTests = hasSnapshotTests
166
+ return hasSnapshotTests
167
+ }
168
+
150
169
  // Function that receives a list of known tests for a test service and
151
170
  // returns the ones that belong to the current suite
152
171
  getKnownTestsForSuite (knownTests) {
@@ -231,6 +250,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
231
250
  const isSkipped = event.mode === 'todo' || event.mode === 'skip'
232
251
  if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testName)) {
233
252
  retriedTestsToNumAttempts.set(testName, 0)
253
+ // Retrying snapshots has proven to be problematic, so we'll skip them for now
254
+ // We'll still detect new tests, but we won't retry them.
255
+ // TODO: do not bail out of EFD with the whole test suite
256
+ if (this.getHasSnapshotTests()) {
257
+ log.warn('Early flake detection is disabled for suites with snapshots')
258
+ return
259
+ }
234
260
  for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
235
261
  if (this.global.test) {
236
262
  this.global.test(addEfdStringToTestName(event.testName, retryIndex), event.fn, event.timeout)
@@ -312,7 +338,7 @@ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
312
338
  numSkippedSuites = jestSuitesToRun.skippedSuites.length
313
339
 
314
340
  itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
315
- skippableSuites = []
341
+
316
342
  return jestSuitesToRun.suitesToRun
317
343
  }
318
344
 
@@ -410,6 +436,7 @@ function cliWrapper (cli, jestVersion) {
410
436
  isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
411
437
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
412
438
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
439
+ earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
413
440
  }
414
441
  } catch (err) {
415
442
  log.error(err)
@@ -524,6 +551,7 @@ function cliWrapper (cli, jestVersion) {
524
551
  hasForcedToRunSuites,
525
552
  error,
526
553
  isEarlyFlakeDetectionEnabled,
554
+ isEarlyFlakeDetectionFaulty,
527
555
  onDone
528
556
  })
529
557
  })
@@ -760,13 +788,26 @@ addHook({
760
788
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
761
789
 
762
790
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
763
- if (!isSuitesSkippingEnabled || !skippableSuites.length) {
764
- return getTestPaths.apply(this, arguments)
765
- }
766
-
791
+ const testPaths = await getTestPaths.apply(this, arguments)
767
792
  const [{ rootDir, shard }] = arguments
768
793
 
769
- if (shard?.shardCount > 1) {
794
+ if (isEarlyFlakeDetectionEnabled) {
795
+ const projectSuites = testPaths.tests.map(test => getTestSuitePath(test.path, test.context.config.rootDir))
796
+ const isFaulty =
797
+ getIsFaultyEarlyFlakeDetection(projectSuites, knownTests.jest || {}, earlyFlakeDetectionFaultyThreshold)
798
+ if (isFaulty) {
799
+ log.error('Early flake detection is disabled because the number of new suites is too high.')
800
+ isEarlyFlakeDetectionEnabled = false
801
+ const testEnvironmentOptions = testPaths.tests[0]?.context?.config?.testEnvironmentOptions
802
+ // Project config is shared among all tests, so we can modify it here
803
+ if (testEnvironmentOptions) {
804
+ testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled = false
805
+ }
806
+ isEarlyFlakeDetectionFaulty = true
807
+ }
808
+ }
809
+
810
+ if (shard?.shardCount > 1 || !isSuitesSkippingEnabled || !skippableSuites.length) {
770
811
  // If the user is using jest sharding, we want to apply the filtering of tests in the shard process.
771
812
  // The reason for this is the following:
772
813
  // The tests for different shards are likely being run in different CI jobs so
@@ -774,10 +815,8 @@ addHook({
774
815
  // If the skippable endpoint is returning different suites and we filter the list of tests here,
775
816
  // the base list of tests that is used for sharding might be different,
776
817
  // causing the shards to potentially run the same suite.
777
- return getTestPaths.apply(this, arguments)
818
+ return testPaths
778
819
  }
779
-
780
- const testPaths = await getTestPaths.apply(this, arguments)
781
820
  const { tests } = testPaths
782
821
 
783
822
  const suitesToRun = applySuiteSkipping(tests, rootDir, frameworkVersion)
@@ -837,6 +876,34 @@ if (DD_MAJOR < 4) {
837
876
  }, jasmineAsyncInstallWraper)
838
877
  }
839
878
 
879
+ const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = [
880
+ 'selenium-webdriver'
881
+ ]
882
+
883
+ function shouldBypassJestRequireEngine (moduleName) {
884
+ return (
885
+ LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.some(library => moduleName.includes(library))
886
+ )
887
+ }
888
+
889
+ addHook({
890
+ name: 'jest-runtime',
891
+ versions: ['>=24.8.0']
892
+ }, (runtimePackage) => {
893
+ const Runtime = runtimePackage.default ? runtimePackage.default : runtimePackage
894
+
895
+ shimmer.wrap(Runtime.prototype, 'requireModuleOrMock', requireModuleOrMock => function (from, moduleName) {
896
+ // TODO: do this for every library that we instrument
897
+ if (shouldBypassJestRequireEngine(moduleName)) {
898
+ // To bypass jest's own require engine
899
+ return this._requireCoreModule(moduleName)
900
+ }
901
+ return requireModuleOrMock.apply(this, arguments)
902
+ })
903
+
904
+ return runtimePackage
905
+ })
906
+
840
907
  addHook({
841
908
  name: 'jest-worker',
842
909
  versions: ['>=24.9.0'],
@@ -21,7 +21,8 @@ addHook({
21
21
  name: 'mongoose',
22
22
  versions: ['>=4.6.4 <5', '5', '6', '>=7']
23
23
  }, mongoose => {
24
- if (mongoose.Promise !== global.Promise) {
24
+ // As of Mongoose 7, custom promise libraries are no longer supported and mongoose.Promise may be undefined
25
+ if (mongoose.Promise && mongoose.Promise !== global.Promise) {
25
26
  shimmer.wrap(mongoose.Promise.prototype, 'then', wrapThen)
26
27
  }
27
28
 
@@ -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
+ }
@@ -4,7 +4,12 @@ const { addHook } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
  const tracer = require('../../dd-trace')
6
6
 
7
- if (process.env.DD_TRACE_OTEL_ENABLED) {
7
+ const otelSdkEnabled = process.env.DD_TRACE_OTEL_ENABLED ||
8
+ process.env.OTEL_SDK_DISABLED
9
+ ? !process.env.OTEL_SDK_DISABLED
10
+ : undefined
11
+
12
+ if (otelSdkEnabled) {
8
13
  addHook({
9
14
  name: '@opentelemetry/sdk-trace-node',
10
15
  file: 'build/src/NodeTracerProvider.js',
@@ -0,0 +1,69 @@
1
+ const { addHook, channel } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+
4
+ const ciSeleniumDriverGetStartCh = channel('ci:selenium:driver:get')
5
+
6
+ const RUM_STOP_SESSION_SCRIPT = `
7
+ if (window.DD_RUM && window.DD_RUM.stopSession) {
8
+ window.DD_RUM.stopSession();
9
+ return true;
10
+ } else {
11
+ return false;
12
+ }
13
+ `
14
+ const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
15
+
16
+ const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = 500
17
+ const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
18
+
19
+ // TODO: can we increase the supported version range?
20
+ addHook({
21
+ name: 'selenium-webdriver',
22
+ versions: ['>=4.11.0']
23
+ }, (seleniumPackage, seleniumVersion) => {
24
+ // TODO: do not turn this into async. Use promises
25
+ shimmer.wrap(seleniumPackage.WebDriver.prototype, 'get', get => async function () {
26
+ let traceId
27
+ const setTraceId = (inputTraceId) => {
28
+ traceId = inputTraceId
29
+ }
30
+ const getResult = await get.apply(this, arguments)
31
+
32
+ const isRumActive = await this.executeScript(IS_RUM_ACTIVE_SCRIPT)
33
+ const capabilities = await this.getCapabilities()
34
+
35
+ ciSeleniumDriverGetStartCh.publish({
36
+ setTraceId,
37
+ seleniumVersion,
38
+ browserName: capabilities.getBrowserName(),
39
+ browserVersion: capabilities.getBrowserVersion(),
40
+ isRumActive
41
+ })
42
+
43
+ await this.manage().addCookie({
44
+ name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
45
+ value: traceId
46
+ })
47
+
48
+ return getResult
49
+ })
50
+
51
+ shimmer.wrap(seleniumPackage.WebDriver.prototype, 'quit', quit => async function () {
52
+ const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
53
+
54
+ if (isRumActive) {
55
+ // We'll have time for RUM to flush the events (there's no callback to know when it's done)
56
+ await new Promise(resolve => {
57
+ setTimeout(() => {
58
+ resolve()
59
+ }, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
60
+ })
61
+ }
62
+
63
+ await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
64
+
65
+ return quit.apply(this, arguments)
66
+ })
67
+
68
+ return seleniumPackage
69
+ })
@@ -16,7 +16,7 @@ const {
16
16
  TEST_CODE_OWNERS,
17
17
  ITR_CORRELATION_ID,
18
18
  TEST_SOURCE_FILE,
19
- TEST_EARLY_FLAKE_IS_ENABLED,
19
+ TEST_EARLY_FLAKE_ENABLED,
20
20
  TEST_IS_NEW,
21
21
  TEST_IS_RETRY
22
22
  } = require('../../dd-trace/src/plugins/util/test')
@@ -68,7 +68,7 @@ class CucumberPlugin extends CiPlugin {
68
68
  }
69
69
  )
70
70
  if (isEarlyFlakeDetectionEnabled) {
71
- this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
71
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
72
72
  }
73
73
 
74
74
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -28,7 +28,7 @@ const {
28
28
  TEST_SOURCE_FILE,
29
29
  TEST_IS_NEW,
30
30
  TEST_IS_RETRY,
31
- TEST_EARLY_FLAKE_IS_ENABLED
31
+ TEST_EARLY_FLAKE_ENABLED
32
32
  } = require('../../dd-trace/src/plugins/util/test')
33
33
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
34
34
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -364,7 +364,7 @@ class CypressPlugin {
364
364
  getTestModuleCommonTags(this.command, this.frameworkVersion, TEST_FRAMEWORK_NAME)
365
365
 
366
366
  if (this.isEarlyFlakeDetectionEnabled) {
367
- testSessionSpanMetadata[TEST_EARLY_FLAKE_IS_ENABLED] = 'true'
367
+ testSessionSpanMetadata[TEST_EARLY_FLAKE_ENABLED] = 'true'
368
368
  }
369
369
 
370
370
  this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
@@ -4,6 +4,16 @@ let knownTestsForSuite = []
4
4
  let suiteTests = []
5
5
  let earlyFlakeDetectionNumRetries = 0
6
6
 
7
+ // If the test is using multi domain with cy.origin, trying to access
8
+ // window properties will result in a cross origin error.
9
+ function safeGetRum (window) {
10
+ try {
11
+ return window.DD_RUM
12
+ } catch (e) {
13
+ return null
14
+ }
15
+ }
16
+
7
17
  function isNewTest (test) {
8
18
  return !knownTestsForSuite.includes(test.fullTitle())
9
19
  }
@@ -62,7 +72,7 @@ before(function () {
62
72
 
63
73
  after(() => {
64
74
  cy.window().then(win => {
65
- if (win.DD_RUM) {
75
+ if (safeGetRum(win)) {
66
76
  win.dispatchEvent(new Event('beforeunload'))
67
77
  }
68
78
  })
@@ -84,9 +94,15 @@ afterEach(function () {
84
94
  testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
85
95
  } catch (e) {}
86
96
 
87
- if (win.DD_RUM) {
97
+ if (safeGetRum(win)) {
88
98
  testInfo.isRUMActive = true
89
99
  }
90
- cy.task('dd:afterEach', { test: testInfo, coverage: win.__coverage__ })
100
+ let coverage
101
+ try {
102
+ coverage = win.__coverage__
103
+ } catch (e) {
104
+ // ignore error and continue
105
+ }
106
+ cy.task('dd:afterEach', { test: testInfo, coverage })
91
107
  })
92
108
  })