dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -8,6 +8,17 @@ const {
8
8
  channel,
9
9
  addHook,
10
10
  } = require('./helpers/instrument')
11
+ const {
12
+ clearPoolWaitTime,
13
+ dispatchesAcquireSynchronously,
14
+ isPoolQueryAcquire,
15
+ runOutsidePoolQueryAcquire,
16
+ runPoolAcquireError,
17
+ runWithPoolWait,
18
+ takePoolWaitTime,
19
+ wrapPoolAcquireCarrier,
20
+ wrapPoolQueryMethod,
21
+ } = require('./helpers/pool-acquire')
11
22
 
12
23
  const startCh = channel('apm:pg:query:start')
13
24
  const finishCh = channel('apm:pg:query:finish')
@@ -21,20 +32,16 @@ const poolConnectFinishCh = channel('apm:pg:pool:connect:finish')
21
32
 
22
33
  const poolAcquireStartCh = channel('apm:pg:pool:acquire:start')
23
34
  const poolAcquireFinishCh = channel('apm:pg:pool:acquire:finish')
35
+ const poolAcquireChannels = {
36
+ connectionFinishCh: poolConnectFinishCh,
37
+ acquireStartCh: poolAcquireStartCh,
38
+ acquireFinishCh: poolAcquireFinishCh,
39
+ }
24
40
 
25
41
  // Drivers like pg-promise reuse the same prepared-statement query object across executions; cache
26
42
  // the un-injected `text` so the wrap doesn't capture a previous DBM injection as the new original.
27
43
  const originalTextCache = new WeakMap()
28
44
 
29
- // Pool.query dispatches its client query synchronously from the connect callback, including nested
30
- // `_release` queue pulses, so the wait can cross that callback without retaining the client.
31
- const poolWaitClients = []
32
- const poolWaitTimes = []
33
-
34
- // Pool.query reports its internal acquire on the query span; explicit connects get an acquire span.
35
- // Pool.query calls connect synchronously, so a module flag reliably distinguishes the two.
36
- let acquiringForPoolQuery = false
37
-
38
45
  addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/native/client.js' }, Client => {
39
46
  shimmer.wrap(Client.prototype, 'query', query => wrapQuery(query))
40
47
  return Client
@@ -46,79 +53,118 @@ addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/client.js' }, Client =>
46
53
  })
47
54
 
48
55
  addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
56
+ const poolWaitTransfer = { deferred: false }
57
+ const deferredPoolAcquire = !dispatchesAcquireSynchronously(
58
+ pg.Pool.prototype.query,
59
+ Object.assign(Object.create(pg.Pool.prototype), { Promise }),
60
+ 'connect',
61
+ ['SELECT 1', () => {}]
62
+ )
63
+
49
64
  // pg defers a busy pool's connect callback and runs it in the releasing query's async context;
50
65
  // capture the caller's context and restore it around the callback so spans attach to the caller.
51
- shimmer.wrap(pg.Pool.prototype, 'connect', connect => function (cb) {
52
- // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
53
- if (typeof cb !== 'function') {
54
- if (!poolAcquireStartCh.hasSubscribers) {
66
+ shimmer.wrap(pg.Pool.prototype, 'connect', connect => {
67
+ /**
68
+ * @param {Function|undefined} callback
69
+ * @returns {unknown}
70
+ */
71
+ const wrappedConnect = function (callback) {
72
+ // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
73
+ if (typeof callback !== 'function') {
74
+ if (!poolAcquireStartCh.hasSubscribers) {
75
+ return connect.apply(this, arguments)
76
+ }
77
+
78
+ const start = acquireStart(this)
79
+ const acquireCtx = { poolOptions: this.options }
80
+ poolAcquireStartCh.publish(acquireCtx)
81
+
82
+ return connectForAcquire(connect, this, arguments, acquireCtx, start).then(client => {
83
+ clearPoolWaitTime(client)
84
+ acquireCtx.params = client.connectionParameters
85
+ finishAcquire(acquireCtx, start)
86
+ return client
87
+ }, error => {
88
+ acquireCtx.error = error
89
+ finishAcquire(acquireCtx, start)
90
+ throw error
91
+ })
92
+ }
93
+
94
+ if (!poolConnectStartCh.hasSubscribers) {
55
95
  return connect.apply(this, arguments)
56
96
  }
57
97
 
98
+ const ctx = {}
99
+ const poolOptions = this.options
58
100
  const start = acquireStart(this)
59
- const acquireCtx = { poolOptions: this.options }
60
- poolAcquireStartCh.publish(acquireCtx)
61
-
62
- return connectForAcquire(connect, this, arguments, acquireCtx, start).then(client => {
63
- acquireCtx.params = client.connectionParameters
64
- finishAcquire(acquireCtx, start)
65
- return client
66
- }, error => {
67
- acquireCtx.error = error
68
- finishAcquire(acquireCtx, start)
69
- throw error
70
- })
71
- }
101
+ const poolQueryAcquire = isPoolQueryAcquire()
102
+ let acquireCtx
72
103
 
73
- if (!poolConnectStartCh.hasSubscribers) {
74
- return connect.apply(this, arguments)
75
- }
76
-
77
- const ctx = {}
78
- const start = acquireStart(this)
79
- let acquireCtx
80
-
81
- if (acquiringForPoolQuery) {
82
- arguments[0] = function (...args) {
83
- const client = args[1]
84
- if (client !== undefined) {
85
- poolWaitClients.push(client)
86
- poolWaitTimes.push(acquireWait(start))
87
- }
88
- try {
89
- return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
90
- } finally {
104
+ if (poolQueryAcquire) {
105
+ arguments[0] = function (...args) {
106
+ const client = args[1]
91
107
  if (client !== undefined) {
92
- poolWaitClients.pop()
93
- poolWaitTimes.pop()
108
+ const waitTime = acquireWait(start)
109
+ return runWithPoolWait(
110
+ poolWaitTransfer,
111
+ client,
112
+ waitTime,
113
+ poolConnectFinishCh,
114
+ ctx,
115
+ callback,
116
+ this,
117
+ args
118
+ )
119
+ }
120
+ if (args[0] && poolAcquireStartCh.hasSubscribers) {
121
+ return runPoolAcquireError(
122
+ start,
123
+ args[0],
124
+ { poolOptions },
125
+ poolAcquireChannels,
126
+ ctx,
127
+ callback,
128
+ this,
129
+ args
130
+ )
94
131
  }
132
+ return poolConnectFinishCh.runStores(ctx, callback, this, ...args)
95
133
  }
96
- }
97
- } else {
98
- acquireCtx = { poolOptions: this.options }
99
- poolAcquireStartCh.publish(acquireCtx)
100
-
101
- arguments[0] = function (...args) {
102
- const client = args[1]
103
- acquireCtx.error = args[0]
104
- // A failed acquire has no client, so keep the parameters `connectForAcquire` snapshotted.
105
- if (client !== undefined) {
106
- acquireCtx.params = client.connectionParameters
134
+ } else {
135
+ acquireCtx = { poolOptions: this.options }
136
+ poolAcquireStartCh.publish(acquireCtx)
137
+
138
+ arguments[0] = function (...args) {
139
+ const client = args[1]
140
+ acquireCtx.error = args[0]
141
+ // A failed acquire has no client, so keep the parameters `connectForAcquire` snapshotted.
142
+ if (client !== undefined) {
143
+ clearPoolWaitTime(client)
144
+ acquireCtx.params = client.connectionParameters
145
+ }
146
+ finishAcquire(acquireCtx, start)
147
+ return poolConnectFinishCh.runStores(ctx, callback, this, ...args)
107
148
  }
108
- finishAcquire(acquireCtx, start)
109
- return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
110
149
  }
111
- }
112
150
 
113
- poolConnectStartCh.publish(ctx)
151
+ poolConnectStartCh.publish(ctx)
114
152
 
115
- if (acquireCtx === undefined) {
116
- return connect.apply(this, arguments)
153
+ if (acquireCtx === undefined) return runOutsidePoolQueryAcquire(connect, this, arguments)
154
+
155
+ return connectForAcquire(connect, this, arguments, acquireCtx, start)
117
156
  }
118
157
 
119
- return connectForAcquire(connect, this, arguments, acquireCtx, start)
158
+ return wrapPoolAcquireCarrier(
159
+ wrappedConnect,
160
+ connect,
161
+ poolConnectStartCh,
162
+ deferredPoolAcquire
163
+ )
120
164
  })
121
- shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
165
+ shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(
166
+ wrapPoolQueryMethod(query, poolConnectStartCh, deferredPoolAcquire)
167
+ ))
122
168
  return pg
123
169
  })
124
170
 
@@ -153,14 +199,9 @@ function wrapQuery (query) {
153
199
  stream,
154
200
  }
155
201
 
156
- if (poolWaitClients.length !== 0) {
157
- for (let i = poolWaitClients.length - 1; i >= 0; i--) {
158
- if (poolWaitClients[i] === this) {
159
- ctx.poolWaitTime = poolWaitTimes[i]
160
- poolWaitClients[i] = undefined
161
- break
162
- }
163
- }
202
+ const waitTime = takePoolWaitTime(this)
203
+ if (waitTime !== undefined) {
204
+ ctx.poolWaitTime = waitTime
164
205
  }
165
206
 
166
207
  const finish = (error, res) => {
@@ -342,25 +383,10 @@ function finishAcquire (ctx, start) {
342
383
  poolAcquireFinishCh.publish(ctx)
343
384
  }
344
385
 
345
- /**
346
- * @param {Function} query
347
- * @param {InstrumentedPool} pool
348
- * @param {unknown[]} args
349
- */
350
- function acquireForPoolQuery (query, pool, args) {
351
- acquiringForPoolQuery = true
352
- try {
353
- return query.apply(pool, args)
354
- } finally {
355
- acquiringForPoolQuery = false
356
- }
357
- }
358
386
  function wrapPoolQuery (query) {
359
387
  return function (...args) {
360
388
  if (!startPoolQueryCh.hasSubscribers) {
361
- return poolConnectStartCh.hasSubscribers
362
- ? acquireForPoolQuery(query, this, args)
363
- : query.apply(this, args)
389
+ return query.apply(this, args)
364
390
  }
365
391
 
366
392
  const pgQuery = args[0] !== null && typeof args[0] === 'object' ? args[0] : { text: args[0] }
@@ -390,9 +416,7 @@ function wrapPoolQuery (query) {
390
416
  })
391
417
  }
392
418
 
393
- const retval = poolConnectStartCh.hasSubscribers
394
- ? acquireForPoolQuery(query, this, args)
395
- : query.apply(this, args)
419
+ const retval = query.apply(this, args)
396
420
 
397
421
  if (retval?.then) {
398
422
  retval.then(() => {
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const reporterErrorCh = channel('ci:playwright:reporter:error')
6
+ const PLAYWRIGHT_REPORTER_ERROR_MESSAGE = 'Error in reporter'
7
+ const PLAYWRIGHT_REPORTER_ERROR_CALLER_RE =
8
+ /^\s*at wrapAsync .*?[\\/]playwright[\\/]lib[\\/]runner[\\/]index\.js:\d+:\d+\)?$/
9
+
10
+ /**
11
+ * Identifies the private Playwright 1.60-1.61 reporter error fallback without
12
+ * interpreting identical user console output as a framework error.
13
+ *
14
+ * @param {unknown} message - First console.error argument
15
+ * @returns {boolean}
16
+ */
17
+ function isPlaywrightReporterError (message) {
18
+ if (message !== PLAYWRIGHT_REPORTER_ERROR_MESSAGE) return false
19
+
20
+ const originalPrepareStackTrace = Error.prepareStackTrace
21
+ try {
22
+ Error.prepareStackTrace = (_, callSites) => callSites
23
+ const stack = new Error('Playwright reporter error provenance').stack
24
+ return PLAYWRIGHT_REPORTER_ERROR_CALLER_RE.test(`at ${stack?.[2]?.toString() || ''}`)
25
+ } finally {
26
+ Error.prepareStackTrace = originalPrepareStackTrace
27
+ }
28
+ }
29
+
30
+ class DatadogPlaywrightReporter {
31
+ /**
32
+ * Restores console error after Playwright completes the reporter lifecycle.
33
+ *
34
+ * @returns {void}
35
+ */
36
+ static restoreConsoleError () {
37
+ // eslint-disable-next-line no-console
38
+ if (console.error === DatadogPlaywrightReporter.consoleError) {
39
+ try {
40
+ // eslint-disable-next-line no-console
41
+ console.error = DatadogPlaywrightReporter.originalConsoleError
42
+ } catch {}
43
+ }
44
+ DatadogPlaywrightReporter.consoleError = undefined
45
+ DatadogPlaywrightReporter.originalConsoleError = undefined
46
+ }
47
+
48
+ /**
49
+ * Marks the beginning of reporter finalization so later reporter errors can be identified.
50
+ *
51
+ * @returns {void}
52
+ */
53
+ onEnd () {
54
+ this.isFinalizing = true
55
+ // Playwright 1.60 and 1.61 only expose reporter errors through this exact console call.
56
+ // eslint-disable-next-line no-console
57
+ const originalConsoleError = console.error
58
+ const reporter = this
59
+ const consoleError = function (message, error) {
60
+ if (isPlaywrightReporterError(message)) {
61
+ reporter.onError(error)
62
+ }
63
+ return originalConsoleError.apply(this, arguments)
64
+ }
65
+ try {
66
+ // eslint-disable-next-line no-console
67
+ console.error = consoleError
68
+ } catch {
69
+ return
70
+ }
71
+ // An accessor may ignore the assignment without throwing.
72
+ // eslint-disable-next-line no-console
73
+ if (console.error !== consoleError) return
74
+
75
+ DatadogPlaywrightReporter.originalConsoleError = originalConsoleError
76
+ DatadogPlaywrightReporter.consoleError = consoleError
77
+ }
78
+
79
+ /**
80
+ * Reports errors emitted by Playwright while later reporters are finalizing.
81
+ *
82
+ * @param {unknown} error
83
+ * @returns {void}
84
+ */
85
+ onError (error) {
86
+ if (this.isFinalizing) reporterErrorCh.publish(error)
87
+ }
88
+
89
+ /**
90
+ * Keeps the internal reporter from affecting Playwright's output reporter selection.
91
+ *
92
+ * @returns {boolean}
93
+ */
94
+ printsToStdio () {
95
+ return false
96
+ }
97
+ }
98
+
99
+ module.exports = DatadogPlaywrightReporter
@@ -45,6 +45,7 @@ const testSkipCh = channel('ci:playwright:test:skip')
45
45
  const testSessionStartCh = channel('ci:playwright:session:start')
46
46
  const testSessionConfigurationCh = channel('ci:playwright:session:configuration')
47
47
  const testSessionFinishCh = channel('ci:playwright:session:finish')
48
+ const reporterErrorCh = channel('ci:playwright:reporter:error')
48
49
 
49
50
  const libraryConfigurationCh = channel('ci:playwright:library-configuration')
50
51
  const knownTestsCh = channel('ci:playwright:known-tests')
@@ -81,6 +82,8 @@ const isFailureScreenshotUploadEnabled =
81
82
  getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
82
83
 
83
84
  let applyRepeatEachIndex = null
85
+ let reporterError
86
+ let hasReporterError = false
84
87
 
85
88
  let startedSuites = []
86
89
 
@@ -134,8 +137,10 @@ const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
134
137
  const DD_PROPERTIES_RESPONSE = 'ddProperties'
135
138
  const kDdPlaywrightDisabledTestIds = Symbol('ddPlaywrightDisabledTestIds')
136
139
  const kDdPlaywrightFailureScreenshots = Symbol('ddPlaywrightFailureScreenshots')
140
+ const kDdPlaywrightReporterConfigured = Symbol('ddPlaywrightReporterConfigured')
137
141
  const kDdPlaywrightWorkerHostInstrumented = Symbol('ddPlaywrightWorkerHostInstrumented')
138
142
  const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
143
+ const instrumentedPlaywrightReporters = new WeakSet()
139
144
  const PLAYWRIGHT_FAILURE_SCREENSHOT_PATH_RE = /(?:^|[\\/])test-failed-\d+\.png$/
140
145
  const automaticFailureScreenshotPaths = new Set()
141
146
 
@@ -1252,6 +1257,17 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
1252
1257
  function runAllTestsWrapper (runAllTests, playwrightVersion) {
1253
1258
  // Config parameter is only available from >=1.55.0
1254
1259
  return async function (config) {
1260
+ reporterError = undefined
1261
+ hasReporterError = false
1262
+ let restoreReporterConsoleError
1263
+ if (satisfies(playwrightVersion, '>=1.60.0') && config?.config) {
1264
+ const DatadogPlaywrightReporter = require('./playwright-reporter')
1265
+ restoreReporterConsoleError = DatadogPlaywrightReporter.restoreConsoleError
1266
+ if (!config.config[kDdPlaywrightReporterConfigured]) {
1267
+ Object.defineProperty(config.config, kDdPlaywrightReporterConfigured, { value: true })
1268
+ config.config.reporter.unshift([require.resolve('./playwright-reporter')])
1269
+ }
1270
+ }
1255
1271
  rootDir = getRootDir(this, config)
1256
1272
  const projects = getProjectsFromRunner(this, config)
1257
1273
  const isFailureScreenshotEnabled = isFailureScreenshotCaptureEnabled(projects)
@@ -1367,7 +1383,27 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1367
1383
  }
1368
1384
  }
1369
1385
 
1370
- let runAllTestsReturn = await runAllTests.apply(this, arguments)
1386
+ let runAllTestsReturn
1387
+ try {
1388
+ runAllTestsReturn = await runAllTests.apply(this, arguments)
1389
+ } catch (error) {
1390
+ try {
1391
+ await getChannelPromise(testSessionFinishCh, {
1392
+ status: 'fail',
1393
+ error: hasReporterError ? reporterError : error,
1394
+ isEarlyFlakeDetectionEnabled,
1395
+ isEarlyFlakeDetectionFaulty,
1396
+ isTestManagementTestsEnabled,
1397
+ })
1398
+ } catch (finalizationError) {
1399
+ log.error('Playwright test session finalization error: %s', finalizationError)
1400
+ }
1401
+ reporterError = undefined
1402
+ hasReporterError = false
1403
+ throw error
1404
+ } finally {
1405
+ restoreReporterConsoleError?.()
1406
+ }
1371
1407
 
1372
1408
  // Tests that have only skipped tests may reach this point
1373
1409
  // Skipped tests may or may not go through `testBegin` or `testEnd`
@@ -1422,12 +1458,21 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1422
1458
  logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
1423
1459
  loggedAttemptToFixTests.clear()
1424
1460
 
1461
+ const finalizationError = reporterError
1462
+ const finalStatus = hasReporterError
1463
+ ? 'fail'
1464
+ : (preventedToFail ? 'pass' : STATUS_TO_TEST_STATUS[sessionStatus])
1425
1465
  await getChannelPromise(testSessionFinishCh, {
1426
- status: preventedToFail ? 'pass' : STATUS_TO_TEST_STATUS[sessionStatus],
1466
+ status: finalStatus,
1467
+ error: finalizationError,
1427
1468
  isEarlyFlakeDetectionEnabled,
1428
1469
  isEarlyFlakeDetectionFaulty,
1429
1470
  isTestManagementTestsEnabled,
1430
1471
  })
1472
+ if (hasReporterError) {
1473
+ if (typeof runAllTestsReturn === 'string') runAllTestsReturn = 'failed'
1474
+ else runAllTestsReturn.status = 'failed'
1475
+ }
1431
1476
 
1432
1477
  startedSuites = []
1433
1478
  remainingTestsByFile = {}
@@ -1444,6 +1489,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1444
1489
  ddPropertiesByTestId.clear()
1445
1490
  ddPropertiesRequestsByTestId.clear()
1446
1491
  disabledTestIds.clear()
1492
+ reporterError = undefined
1493
+ hasReporterError = false
1447
1494
 
1448
1495
  // TODO: we can trick playwright into thinking the session passed by returning
1449
1496
  // 'passed' here. We might be able to use this for both EFD and Test Management tests.
@@ -1451,6 +1498,68 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1451
1498
  }
1452
1499
  }
1453
1500
 
1501
+ /**
1502
+ * Records errors thrown or rejected by Playwright reporters before Playwright converts them into run status.
1503
+ *
1504
+ * @param {object} reportersPackage
1505
+ * @returns {object}
1506
+ */
1507
+ function reportersHook (reportersPackage) {
1508
+ return shimmer.wrap(reportersPackage, 'createReporters', createReporters => async function () {
1509
+ const reporters = await createReporters.apply(this, arguments)
1510
+ for (const reporter of reporters) {
1511
+ if (instrumentedPlaywrightReporters.has(reporter)) continue
1512
+
1513
+ const onEnd = reporter.onEnd
1514
+ const onExit = reporter.onExit
1515
+ try {
1516
+ if (typeof onEnd === 'function') {
1517
+ reporter.onEnd = async function () {
1518
+ try {
1519
+ return await onEnd.apply(this, arguments)
1520
+ } catch (error) {
1521
+ recordReporterError(error)
1522
+ throw error
1523
+ }
1524
+ }
1525
+ }
1526
+ if (typeof onExit === 'function') {
1527
+ reporter.onExit = async function () {
1528
+ try {
1529
+ return await onExit.apply(this, arguments)
1530
+ } catch (error) {
1531
+ recordReporterError(error)
1532
+ throw error
1533
+ }
1534
+ }
1535
+ }
1536
+ } catch {
1537
+ continue
1538
+ }
1539
+ if (reporter.onEnd !== onEnd || reporter.onExit !== onExit) instrumentedPlaywrightReporters.add(reporter)
1540
+ }
1541
+ return reporters
1542
+ }, { replaceGetter: true })
1543
+ }
1544
+
1545
+ /**
1546
+ * Records errors from the reporter multiplexer used by Playwright before 1.38.
1547
+ *
1548
+ * @param {object} reportersPackage
1549
+ * @returns {object}
1550
+ */
1551
+ function reporterMultiplexerHook (reportersPackage) {
1552
+ shimmer.wrap(reportersPackage.Multiplexer.prototype, 'onEnd', onEnd => async function () {
1553
+ try {
1554
+ return await onEnd.apply(this, arguments)
1555
+ } catch (error) {
1556
+ recordReporterError(error)
1557
+ throw error
1558
+ }
1559
+ })
1560
+ return reportersPackage
1561
+ }
1562
+
1454
1563
  function runnerHook (runnerExport, playwrightVersion) {
1455
1564
  shimmer.wrap(
1456
1565
  runnerExport.Runner.prototype,
@@ -1550,6 +1659,32 @@ pageGotoCh.subscribe({
1550
1659
  },
1551
1660
  })
1552
1661
 
1662
+ reporterErrorCh.subscribe((error) => {
1663
+ recordReporterError(error)
1664
+ })
1665
+
1666
+ /**
1667
+ * Records a reporter failure even when the reporter throws a falsy value.
1668
+ *
1669
+ * @param {unknown} error
1670
+ * @returns {void}
1671
+ */
1672
+ function recordReporterError (error) {
1673
+ if (hasReporterError) return
1674
+
1675
+ let normalizedError
1676
+ try {
1677
+ const errorObject = error && typeof error === 'object' ? error : undefined
1678
+ normalizedError = new Error(errorObject?.message || errorObject?.value || String(error))
1679
+ normalizedError.name = errorObject?.name || normalizedError.name
1680
+ normalizedError.stack = errorObject?.stack || normalizedError.stack
1681
+ } catch {
1682
+ normalizedError = new Error('Playwright reporter failed')
1683
+ }
1684
+ reporterError = normalizedError
1685
+ hasReporterError = true
1686
+ }
1687
+
1553
1688
  /**
1554
1689
  * Records a path created by Playwright's automatic screenshot recorder.
1555
1690
  *
@@ -1597,6 +1732,12 @@ if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1597
1732
  file: 'lib/runner/runner.js',
1598
1733
  versions: ['>=1.31.0 <1.38.0'],
1599
1734
  }, runnerHook)
1735
+
1736
+ addHook({
1737
+ name: '@playwright/test',
1738
+ file: 'lib/reporters/multiplexer.js',
1739
+ versions: ['>=1.18.0 <1.38.0'],
1740
+ }, reporterMultiplexerHook)
1600
1741
  }
1601
1742
 
1602
1743
  addHook({
@@ -1617,6 +1758,12 @@ addHook({
1617
1758
  versions: ['>=1.38.0 <1.60.0'],
1618
1759
  }, runnerHook)
1619
1760
 
1761
+ addHook({
1762
+ name: 'playwright',
1763
+ file: 'lib/runner/reporters.js',
1764
+ versions: ['>=1.38.0 <1.60.0'],
1765
+ }, reportersHook)
1766
+
1620
1767
  addHook({
1621
1768
  name: 'playwright',
1622
1769
  file: 'lib/runner/testRunner.js',
@@ -596,6 +596,7 @@ const visitedParams = new WeakSet()
596
596
  function wrapHandleRequest (original) {
597
597
  return function wrappedHandleRequest (...args) {
598
598
  const req = args[0]
599
+ // eslint-disable-next-line no-restricted-syntax -- arbitrary param names; publishing {} sets a WAF address
599
600
  if (routerParamStartCh.hasSubscribers && !visitedParams.has(req.params) && Object.keys(req.params).length) {
600
601
  visitedParams.add(req.params)
601
602
 
@@ -635,6 +636,7 @@ function wrapParam (original) {
635
636
  args[1] = shimmer.wrapFunction(args[1], (originalFn) => {
636
637
  return function wrappedFn (...fnArgs) {
637
638
  const req = fnArgs[0]
639
+ // eslint-disable-next-line no-restricted-syntax -- arbitrary param names; publishing {} sets a WAF address
638
640
  if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) {
639
641
  visitedParams.add(req.params)
640
642