dd-trace 6.8.0 → 6.9.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 (179) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -1,7 +1,16 @@
1
1
  'use strict'
2
2
 
3
+ const { fileURLToPath } = require('node:url')
4
+
5
+ const { channel } = require('dc-polyfill')
6
+
3
7
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
4
8
  const { storage } = require('../../datadog-core')
9
+ const log = require('../../dd-trace/src/log')
10
+ const {
11
+ sendWebdriverioWorkerMessage,
12
+ SUITE_FINISH,
13
+ } = require('../../datadog-instrumentations/src/mocha/webdriverio-protocol')
5
14
 
6
15
  const {
7
16
  TEST_STATUS,
@@ -51,6 +60,94 @@ const {
51
60
  TELEMETRY_TEST_SESSION,
52
61
  } = require('../../dd-trace/src/ci-visibility/telemetry')
53
62
 
63
+ const jasmineAdapterRunAsyncEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineAdapter_run:asyncEnd'
64
+ const jasmineDoneCh = 'ci:webdriverio:jasmine:done'
65
+ const jasmineReporterSpecDoneEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_specDone:end'
66
+ const jasmineReporterSpecStartedEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_specStarted:end'
67
+ const jasmineReporterSuiteDoneEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_suiteDone:end'
68
+ const jasmineReporterSuiteStartedEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_suiteStarted:end'
69
+ const jasmineTestFunctionStartCh = 'tracing:orchestrion:@wdio/utils:testFrameworkFnWrapper:start'
70
+ const testFinishCh = channel('ci:mocha:test:finish')
71
+ const WEBDRIVERIO_JASMINE_ADAPTER = 'jasmine'
72
+ const workerFinishCh = channel('ci:mocha:worker:finish')
73
+
74
+ /**
75
+ * @typedef {object} WebdriverioJasmineResult
76
+ * @property {string|undefined} id
77
+ * @property {string|undefined} description
78
+ * @property {Array<{message?: string, stack?: string}>|undefined} failedExpectations
79
+ * @property {string|undefined} file
80
+ * @property {string|undefined} filename
81
+ * @property {string|undefined} fullName
82
+ * @property {string|undefined} parentSuiteId
83
+ * @property {string|undefined} status
84
+ */
85
+
86
+ /**
87
+ * Normalizes a WebdriverIO Jasmine spec identifier to a filesystem path.
88
+ *
89
+ * @param {string|undefined} file
90
+ * @returns {string|undefined}
91
+ */
92
+ function normalizeJasmineFile (file) {
93
+ return file?.startsWith('file://') ? fileURLToPath(file) : file
94
+ }
95
+
96
+ /**
97
+ * Maps a Jasmine result status to the Test Optimization status vocabulary.
98
+ *
99
+ * @param {string|undefined} status
100
+ * @returns {'pass'|'fail'|'skip'}
101
+ */
102
+ function getJasmineStatus (status) {
103
+ if (status === 'passed') return 'pass'
104
+ if (status === 'failed') return 'fail'
105
+ return 'skip'
106
+ }
107
+
108
+ /**
109
+ * Converts Jasmine's failed-expectation shape to an Error.
110
+ *
111
+ * @param {WebdriverioJasmineResult} result
112
+ * @returns {Error|undefined}
113
+ */
114
+ function getJasmineError (result) {
115
+ const failedExpectation = result.failedExpectations?.[0]
116
+ if (!failedExpectation) return
117
+
118
+ const error = new Error(failedExpectation.message)
119
+ if (failedExpectation.stack) {
120
+ error.stack = failedExpectation.stack
121
+ }
122
+ return error
123
+ }
124
+
125
+ /**
126
+ * Resolves the spec file responsible for a run-level Jasmine failure.
127
+ *
128
+ * @param {WebdriverioJasmineResult|undefined} result
129
+ * @param {string[]} specs
130
+ * @returns {string|undefined}
131
+ */
132
+ function getJasmineFailureFile (result, specs) {
133
+ const resultFile = normalizeJasmineFile(result?.file || result?.filename)
134
+ if (resultFile) {
135
+ return resultFile
136
+ }
137
+
138
+ const stack = result?.failedExpectations?.[0]?.stack
139
+ if (stack) {
140
+ for (const spec of specs) {
141
+ const file = normalizeJasmineFile(spec)
142
+ if (file && stack.includes(file)) {
143
+ return file
144
+ }
145
+ }
146
+ }
147
+
148
+ return specs.length === 1 ? normalizeJasmineFile(specs[0]) : undefined
149
+ }
150
+
54
151
  class MochaPlugin extends CiPlugin {
55
152
  static id = 'mocha'
56
153
 
@@ -60,10 +157,98 @@ class MochaPlugin extends CiPlugin {
60
157
  this._testTitleToParams = {}
61
158
  this.sourceRoot = process.cwd()
62
159
 
63
- this.addSub('ci:mocha:worker:configuration', ({ libraryConfig, repositoryRoot, testFramework }) => {
160
+ this.addSub('ci:mocha:worker:configuration', ({
161
+ libraryConfig,
162
+ repositoryRoot,
163
+ specs,
164
+ testFramework,
165
+ testFrameworkAdapter,
166
+ }) => {
64
167
  this.libraryConfig = libraryConfig
65
168
  this.testFramework = testFramework
169
+ this.testFrameworkAdapter = testFrameworkAdapter
66
170
  this._setRepositoryRoot(repositoryRoot)
171
+ if (testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
172
+ this._webdriverioJasmineState = {
173
+ currentResult: undefined,
174
+ specs: specs || [],
175
+ suiteErrors: new Map(),
176
+ suiteFiles: new Map(),
177
+ suiteStatuses: new Map(),
178
+ tests: new Map(),
179
+ }
180
+ }
181
+ })
182
+
183
+ this.addBind(jasmineTestFunctionStartCh, (ctx) => {
184
+ if (this.testFrameworkAdapter !== WEBDRIVERIO_JASMINE_ADAPTER) {
185
+ return storage('legacy').getStore()
186
+ }
187
+
188
+ const result = this._webdriverioJasmineState?.currentResult
189
+ const type = ctx.arguments?.[1]
190
+ if (type === 'Test' || (type === 'Hook' && result)) {
191
+ return this.#startWebdriverioJasmineTest(result)
192
+ }
193
+ return storage('legacy').getStore()
194
+ })
195
+
196
+ this.addSub(jasmineReporterSuiteStartedEndCh, (ctx) => {
197
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
198
+ const suite = ctx.arguments?.[0]
199
+ const file = normalizeJasmineFile(suite?.filename)
200
+ if (suite?.id && file) {
201
+ this._webdriverioJasmineState.suiteFiles.set(suite.id, file)
202
+ }
203
+ }
204
+ })
205
+
206
+ this.addSub(jasmineReporterSuiteDoneEndCh, (ctx) => {
207
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
208
+ const state = this._webdriverioJasmineState
209
+ const suite = ctx.arguments?.[0]
210
+ const error = suite && getJasmineError(suite)
211
+ const file = normalizeJasmineFile(
212
+ suite?.filename ||
213
+ state.suiteFiles.get(suite?.id) ||
214
+ (state.specs.length === 1 ? state.specs[0] : undefined)
215
+ )
216
+ if (error && file) {
217
+ state.suiteErrors.set(file, error)
218
+ state.suiteStatuses.set(file, 'fail')
219
+ }
220
+ }
221
+ })
222
+
223
+ this.addSub(jasmineDoneCh, ({ result } = {}) => {
224
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
225
+ const state = this._webdriverioJasmineState
226
+ const error = result && getJasmineError(result)
227
+ const file = getJasmineFailureFile(result, state.specs)
228
+ if (error && file) {
229
+ state.suiteErrors.set(file, error)
230
+ state.suiteStatuses.set(file, 'fail')
231
+ }
232
+ }
233
+ })
234
+
235
+ this.addSub(jasmineReporterSpecStartedEndCh, (ctx) => {
236
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
237
+ this._webdriverioJasmineState.currentResult = ctx.arguments?.[0]
238
+ this.#startWebdriverioJasmineTest(ctx.arguments?.[0], ctx.self?._specs, ctx.self?.startedSuite)
239
+ }
240
+ })
241
+
242
+ this.addSub(jasmineReporterSpecDoneEndCh, (ctx) => {
243
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
244
+ this.#finishWebdriverioJasmineTest(ctx.arguments?.[0], ctx.self?._specs)
245
+ }
246
+ })
247
+
248
+ this.addSub(jasmineAdapterRunAsyncEndCh, (ctx) => {
249
+ if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
250
+ this.#finishWebdriverioJasmineWorker(ctx)
251
+ }
67
252
  })
68
253
 
69
254
  this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
@@ -484,6 +669,138 @@ class MochaPlugin extends CiPlugin {
484
669
  })
485
670
  }
486
671
 
672
+ /**
673
+ * Starts a Jasmine test span around WebdriverIO's test-function wrapper.
674
+ *
675
+ * @param {WebdriverioJasmineResult|undefined} result
676
+ * @param {string[]} [specs]
677
+ * @param {{filename?: string}|undefined} currentSuite
678
+ * @returns {object|undefined}
679
+ */
680
+ #startWebdriverioJasmineTest (result, specs, currentSuite) {
681
+ const state = this._webdriverioJasmineState
682
+ const currentStore = storage('legacy').getStore()
683
+ if (!state || !result?.id) {
684
+ return currentStore
685
+ }
686
+
687
+ const existingTest = state.tests.get(result.id)
688
+ if (existingTest) {
689
+ return existingTest.currentStore
690
+ }
691
+
692
+ const candidateSpecs = specs || state.specs
693
+ const testSuiteAbsolutePath = normalizeJasmineFile(
694
+ state.suiteFiles.get(result.parentSuiteId) ||
695
+ currentSuite?.filename ||
696
+ (candidateSpecs.length === 1 ? candidateSpecs[0] : undefined) ||
697
+ result.file ||
698
+ result.filename ||
699
+ candidateSpecs[0]
700
+ )
701
+ if (!testSuiteAbsolutePath) {
702
+ return currentStore
703
+ }
704
+
705
+ const span = this.startTestSpan({
706
+ testName: result.fullName || result.description,
707
+ testSuiteAbsolutePath,
708
+ title: result.description,
709
+ })
710
+ const testStore = { ...currentStore, span }
711
+ state.tests.set(result.id, {
712
+ currentStore: testStore,
713
+ span,
714
+ testSuiteAbsolutePath,
715
+ })
716
+ this.activeTestSpan = span
717
+
718
+ return testStore
719
+ }
720
+
721
+ /**
722
+ * Finishes a Jasmine test after its reporter receives the final result.
723
+ *
724
+ * @param {WebdriverioJasmineResult|undefined} result
725
+ * @param {string[]} [specs]
726
+ * @returns {void}
727
+ */
728
+ #finishWebdriverioJasmineTest (result, specs) {
729
+ const state = this._webdriverioJasmineState
730
+ if (!state || !result?.id) {
731
+ return
732
+ }
733
+
734
+ this.#startWebdriverioJasmineTest(result, specs)
735
+ const test = state.tests.get(result.id)
736
+ if (!test) {
737
+ return
738
+ }
739
+
740
+ const status = getJasmineStatus(result.status)
741
+ const error = getJasmineError(result)
742
+ if (error) {
743
+ test.span.setTag('error', error)
744
+ }
745
+ testFinishCh.publish({ span: test.span, status })
746
+ state.tests.delete(result.id)
747
+ if (state.currentResult?.id === result.id) {
748
+ state.currentResult = undefined
749
+ }
750
+
751
+ const previousStatus = state.suiteStatuses.get(test.testSuiteAbsolutePath)
752
+ if (status === 'fail' || !previousStatus || previousStatus === 'skip') {
753
+ state.suiteStatuses.set(test.testSuiteAbsolutePath, status)
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Reports Jasmine suite statuses and flushes worker traces before its run settles.
759
+ *
760
+ * @param {{
761
+ * resolveCallback?: (onDone: () => void) => void,
762
+ * rejectCallback?: (onDone: () => void) => void
763
+ * }} context
764
+ * @returns {void}
765
+ */
766
+ #finishWebdriverioJasmineWorker (context) {
767
+ const state = this._webdriverioJasmineState
768
+ const results = []
769
+ const reportedFiles = new Set()
770
+ for (const [file, status] of state.suiteStatuses) {
771
+ const error = state.suiteErrors.get(file)
772
+ const result = { file, status }
773
+ if (error) {
774
+ result.error = {
775
+ message: error.message,
776
+ stack: error.stack,
777
+ }
778
+ }
779
+ results.push(result)
780
+ reportedFiles.add(file)
781
+ }
782
+ for (const spec of state.specs) {
783
+ const file = normalizeJasmineFile(spec)
784
+ if (!reportedFiles.has(file)) {
785
+ results.push({ file, status: 'skip' })
786
+ }
787
+ }
788
+
789
+ const waitForWorker = onDone => {
790
+ sendWebdriverioWorkerMessage({
791
+ origin: 'datadog',
792
+ name: SUITE_FINISH,
793
+ content: { results },
794
+ }, error => {
795
+ if (error) {
796
+ log.error('WebdriverIO Test Optimization IPC error', error)
797
+ }
798
+ }, () => workerFinishCh.publish({ onDone }))
799
+ }
800
+ context.resolveCallback = waitForWorker
801
+ context.rejectCallback = waitForWorker
802
+ }
803
+
487
804
  startTestSpan (testInfo) {
488
805
  const {
489
806
  testName,
@@ -5,8 +5,11 @@ const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
+ const { HTTP_ROUTE, RESOURCE_NAME } = require('../../../ext/tags')
8
9
 
9
10
  const errorPages = new Set(['/404', '/500', '/_error', '/_not-found', '/_not-found/page'])
11
+ const reusedNextRequestStores = new WeakSet()
12
+ const nextParentRoutes = new WeakMap()
10
13
 
11
14
  class NextPlugin extends ServerPlugin {
12
15
  static id = 'next'
@@ -18,7 +21,14 @@ class NextPlugin extends ServerPlugin {
18
21
 
19
22
  bindStart ({ req, res }) {
20
23
  const store = storage('legacy').getStore()
21
- const childOf = store ? store.span : store
24
+ const parentSpan = store?.span
25
+ if (parentSpan?._integrationName === this.constructor.id) {
26
+ const reusedStore = { ...store, span: parentSpan, req }
27
+ reusedNextRequestStores.add(reusedStore)
28
+ return reusedStore
29
+ }
30
+
31
+ const childOf = parentSpan || web.extractIncomingServerContext(this.tracer, req.headers)
22
32
  const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
23
33
  const serviceName = this.config.service || schemaServiceName
24
34
  let serviceSource = this.config.service ? 'opt.plugin' : schemaServiceSource
@@ -42,7 +52,10 @@ class NextPlugin extends ServerPlugin {
42
52
 
43
53
  analyticsSampler.sample(span, this.config.measured, true)
44
54
 
45
- return { ...store, span, req }
55
+ const isHttpParent = parentSpan?._integrationName === 'http'
56
+ const httpParentSpan = isHttpParent ? parentSpan : undefined
57
+ const httpParentReq = isHttpParent ? web.getRequest(parentSpan) : undefined
58
+ return { ...store, span, req, httpParentSpan, httpParentReq }
46
59
  }
47
60
 
48
61
  error ({ span, error }) {
@@ -60,6 +73,7 @@ class NextPlugin extends ServerPlugin {
60
73
  const store = storage('legacy').getStore()
61
74
 
62
75
  if (!store) return
76
+ if (reusedNextRequestStores.has(store)) return
63
77
 
64
78
  const span = store.span
65
79
  const error = span.context().getTag('error')
@@ -88,12 +102,12 @@ class NextPlugin extends ServerPlugin {
88
102
  span.finish()
89
103
  }
90
104
 
91
- pageLoad ({ page, isAppPath = false, isStatic = false }) {
105
+ pageLoad ({ page, isAppPath = false, isStatic = false, isFilesystemPath = isAppPath }) {
92
106
  const store = storage('legacy').getStore()
93
107
 
94
108
  if (!store) return
95
109
 
96
- const { span, req } = store
110
+ const { span, req, httpParentReq, httpParentSpan } = store
97
111
 
98
112
  // safeguard against missing req in complicated timeout scenarios
99
113
  if (!req) return
@@ -106,10 +120,11 @@ class NextPlugin extends ServerPlugin {
106
120
  return
107
121
  }
108
122
 
109
- // remove ending /route or /page for appDir projects
110
- // need to check if not an error page too, as those are marked as app directory
111
- // in newer versions
112
- if (isAppPath && !isErrorPage) page = page.slice(0, Math.max(0, page.lastIndexOf('/')))
123
+ // Compiled runtimes may publish a normalized pathname rather than a filesystem path.
124
+ if (isFilesystemPath && !isErrorPage) {
125
+ if (isAppPath) page = normalizeAppPath(page)
126
+ page = normalizeIndexPage(page)
127
+ }
113
128
 
114
129
  // handle static resource
115
130
  if (isStatic) {
@@ -123,7 +138,10 @@ class NextPlugin extends ServerPlugin {
123
138
  'resource.name': `${req.method} ${page}`.trim(),
124
139
  'next.page': page,
125
140
  })
126
- web.setRoute(req, page)
141
+ const routeRequest = httpParentReq || req
142
+ web.setRouteOrEndpointTag(routeRequest)
143
+ setHttpParentRoute(httpParentSpan, req.method, page, isStatic)
144
+ web.setRoute(routeRequest, page)
127
145
  }
128
146
 
129
147
  configure (config) {
@@ -131,6 +149,36 @@ class NextPlugin extends ServerPlugin {
131
149
  }
132
150
  }
133
151
 
152
+ function normalizeIndexPage (page) {
153
+ if (typeof page !== 'string') return page
154
+ if (page === '/index') return '/'
155
+ if (page.endsWith('/index')) return page.slice(0, -'/index'.length) || '/'
156
+ return page
157
+ }
158
+
159
+ function normalizeAppPath (page) {
160
+ if (typeof page !== 'string') return page
161
+
162
+ for (const suffix of ['/page', '/route']) {
163
+ if (page === suffix) return '/'
164
+ if (page.endsWith(suffix)) return page.slice(0, -suffix.length) || '/'
165
+ }
166
+
167
+ return page
168
+ }
169
+
170
+ function setHttpParentRoute (span, method, page, isStatic) {
171
+ if (!span) return
172
+ const currentRoute = span.context().getTag(HTTP_ROUTE)
173
+
174
+ // Only refine a route that Next itself set; static resources are fallbacks.
175
+ if (currentRoute && (nextParentRoutes.get(span) !== currentRoute || isStatic)) return
176
+
177
+ span.setTag(HTTP_ROUTE, page)
178
+ span.setTag(RESOURCE_NAME, `${method} ${page}`.trim())
179
+ nextParentRoutes.set(span, page)
180
+ }
181
+
134
182
  function normalizeConfig (config) {
135
183
  const hooks = getHooks(config)
136
184
  const validateStatus = typeof config.validateStatus === 'function'
@@ -627,6 +627,10 @@ function lookupOperationEndpoint (operationId, methodName, url) {
627
627
  * This function essentially normalizes the OpenAI method interface. Many methods accept
628
628
  * a single object argument. The remaining ones take individual arguments. This function
629
629
  * turns the individual arguments into an object to make extracting properties consistent.
630
+ *
631
+ * @param {string} methodName SDK method name, e.g. `createChatCompletion` or `chat.completions.create`
632
+ * @param {unknown[]} args arguments the SDK method was called with
633
+ * @returns {Record<string, unknown>}
630
634
  */
631
635
  function normalizeRequestPayload (methodName, args) {
632
636
  switch (methodName) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../datadog-core')
4
- const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
4
+ const { CLIENT_PORT_KEY, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
5
5
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
6
 
7
7
  class PGPlugin extends DatabasePlugin {
@@ -16,6 +16,52 @@ class PGPlugin extends DatabasePlugin {
16
16
  ctx.parentStore = storage('legacy').getStore()
17
17
  })
18
18
  this.addBind('apm:pg:pool:connect:finish', ctx => ctx.parentStore)
19
+
20
+ this.addSub('apm:pg:pool:acquire:start', ctx => {
21
+ const params = ctx.poolOptions
22
+ const operationName = this.operationName({ operation: 'pool.acquire' })
23
+ const deferServiceResolution = typeof this.config.service === 'function'
24
+ const service = deferServiceResolution
25
+ ? undefined
26
+ : this.serviceName({ pluginConfig: this.config, params })
27
+ ctx.deferServiceResolution = deferServiceResolution
28
+
29
+ this.startSpan(operationName, {
30
+ service,
31
+ resource: operationName,
32
+ type: 'sql',
33
+ kind: 'client',
34
+ meta: {
35
+ 'db.type': 'postgres',
36
+ ...connectionMeta(params),
37
+ },
38
+ }, ctx)
39
+ })
40
+ this.addSub('apm:pg:pool:acquire:finish', ctx => {
41
+ const span = ctx.currentStore?.span
42
+ if (span === undefined) return
43
+
44
+ if (ctx.error) {
45
+ this.addError(ctx.error, span)
46
+ }
47
+ span.setTag('db.pool.wait_time_ms', ctx.poolWaitTime)
48
+ // `Pool` options carry only what the caller passed, so anything pg resolves later - a
49
+ // connection string, the default port, `PG*` environment variables - is known once the
50
+ // client exists.
51
+ if (ctx.params !== undefined) {
52
+ span.addTags(connectionMeta(ctx.params))
53
+ if (ctx.deferServiceResolution) {
54
+ const service = this.serviceName({ pluginConfig: this.config, params: ctx.params })
55
+ // A `service` callback may return no name, and the start-time fallback already carries
56
+ // the schema default every other pg span falls back to.
57
+ if (service.name) {
58
+ this.setServiceName(span, service.name)
59
+ span.setTag(SVC_SRC_KEY, service.source)
60
+ }
61
+ }
62
+ }
63
+ this.finish(ctx)
64
+ })
19
65
  }
20
66
 
21
67
  bindStart (ctx) {
@@ -42,10 +88,28 @@ class PGPlugin extends DatabasePlugin {
42
88
  span.setTag('db.stream', 1)
43
89
  }
44
90
 
91
+ if (ctx.poolWaitTime !== undefined) {
92
+ span.setTag('db.pool.wait_time_ms', ctx.poolWaitTime)
93
+ }
94
+
45
95
  ctx.injected = this.injectDbmQuery(span, originalText, service.name, !!query.name)
46
96
 
47
97
  return ctx.currentStore
48
98
  }
49
99
  }
50
100
 
101
+ /**
102
+ * `Pool.options` and `Client.connectionParameters` are pg internals, so either can be missing.
103
+ *
104
+ * @param {{ database?: string, user?: string, host?: string, port?: number } | undefined} params
105
+ */
106
+ function connectionMeta (params) {
107
+ return {
108
+ 'db.name': params?.database,
109
+ 'db.user': params?.user,
110
+ 'out.host': params?.host,
111
+ [CLIENT_PORT_KEY]: params?.port,
112
+ }
113
+ }
114
+
51
115
  module.exports = PGPlugin
@@ -485,7 +485,7 @@ class PlaywrightPlugin extends CiPlugin {
485
485
  {
486
486
  hasCodeOwners: !!span.context().getTag(TEST_CODE_OWNERS),
487
487
  isNew,
488
- isRum: isRUMActive,
488
+ isRum: isRUMActive === 'true' || undefined,
489
489
  browserDriver: 'playwright',
490
490
  isQuarantined,
491
491
  isDisabled,