dd-trace 5.86.0 → 5.87.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 (38) hide show
  1. package/index.d.ts +18 -3
  2. package/package.json +1 -1
  3. package/packages/datadog-instrumentations/src/cucumber.js +14 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/http/client.js +119 -1
  6. package/packages/datadog-instrumentations/src/jest.js +104 -4
  7. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  8. package/packages/datadog-instrumentations/src/mysql2.js +131 -64
  9. package/packages/datadog-instrumentations/src/playwright.js +8 -0
  10. package/packages/datadog-instrumentations/src/stripe.js +92 -0
  11. package/packages/datadog-instrumentations/src/vitest.js +11 -0
  12. package/packages/datadog-plugin-azure-functions/src/index.js +53 -37
  13. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
  14. package/packages/dd-trace/src/appsec/addresses.js +11 -0
  15. package/packages/dd-trace/src/appsec/channels.js +5 -1
  16. package/packages/dd-trace/src/appsec/downstream_requests.js +302 -0
  17. package/packages/dd-trace/src/appsec/index.js +103 -0
  18. package/packages/dd-trace/src/appsec/rasp/ssrf.js +66 -4
  19. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +14 -1
  20. package/packages/dd-trace/src/config/defaults.js +2 -0
  21. package/packages/dd-trace/src/config/index.js +6 -0
  22. package/packages/dd-trace/src/config/supported-configurations.json +2 -0
  23. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -2
  24. package/packages/dd-trace/src/debugger/devtools_client/index.js +75 -23
  25. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +23 -1
  26. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +3 -3
  27. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +168 -36
  28. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +18 -0
  29. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  30. package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
  31. package/packages/dd-trace/src/llmobs/sdk.js +34 -5
  32. package/packages/dd-trace/src/plugins/database.js +42 -43
  33. package/packages/dd-trace/src/plugins/outbound.js +27 -2
  34. package/packages/dd-trace/src/plugins/tracing.js +39 -4
  35. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +7 -0
  36. package/packages/dd-trace/src/plugins/util/web.js +8 -7
  37. package/packages/dd-trace/src/startup-log.js +2 -2
  38. package/packages/dd-trace/src/plugins/util/serverless.js +0 -8
@@ -6,6 +6,22 @@ const shimmer = require('../../datadog-shimmer')
6
6
  const satisfies = require('../../../vendor/dist/semifies')
7
7
  const { channel, addHook } = require('./helpers/instrument')
8
8
 
9
+ /** @type {WeakMap<object, Function>} */
10
+ const wrappedOnResult = new WeakMap()
11
+
12
+ /**
13
+ * @param {unknown} sql
14
+ * @returns {string|undefined}
15
+ */
16
+ function resolveSqlString (sql) {
17
+ return typeof sql === 'string' ? sql : /** @type {{ sql?: string }} */ (sql)?.sql
18
+ }
19
+
20
+ /**
21
+ * @param {Function} Connection
22
+ * @param {string} version
23
+ * @returns {Function}
24
+ */
9
25
  function wrapConnection (Connection, version) {
10
26
  const startCh = channel('apm:mysql2:query:start')
11
27
  const finishCh = channel('apm:mysql2:query:finish')
@@ -19,15 +35,27 @@ function wrapConnection (Connection, version) {
19
35
  shimmer.wrap(Connection.prototype, 'addCommand', addCommand => function (cmd) {
20
36
  if (!startCh.hasSubscribers) return addCommand.apply(this, arguments)
21
37
 
22
- const name = cmd && cmd.constructor && cmd.constructor.name
23
- const isCommand = typeof cmd.execute === 'function'
24
- const isQuery = isCommand && (name === 'Execute' || name === 'Query')
38
+ const command = /** @type {{ execute?: Function, constructor?: { name?: string } }} */ (cmd)
39
+ if (typeof command.execute !== 'function') return addCommand.apply(this, arguments)
40
+
41
+ const name = command.constructor?.name
42
+ const isQuery = name === 'Execute' || name === 'Query'
25
43
  const ctx = {}
26
44
 
27
- // TODO: consider supporting all commands and not just queries
28
- cmd.execute = isQuery
29
- ? wrapExecute(cmd, cmd.execute, ctx, this.config)
30
- : bindExecute(cmd.execute, ctx)
45
+ if (isQuery) {
46
+ command.execute = wrapExecute(command, command.execute, ctx, this.config)
47
+
48
+ return commandAddCh.runStores(ctx, addCommand, this, ...arguments)
49
+ }
50
+
51
+ wrapCommandOnResult(command, ctx)
52
+
53
+ command.execute = shimmer.wrapFunction(
54
+ command.execute,
55
+ execute => function executeWithTrace (_packet_, _connection_) {
56
+ return commandStartCh.runStores(ctx, execute, this, ...arguments)
57
+ }
58
+ )
31
59
 
32
60
  return commandAddCh.runStores(ctx, addCommand, this, ...arguments)
33
61
  })
@@ -35,12 +63,11 @@ function wrapConnection (Connection, version) {
35
63
  shimmer.wrap(Connection.prototype, 'query', query => function (sql, values, cb) {
36
64
  if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
37
65
 
38
- if (sql !== null && typeof sql === 'object') sql = sql.sql
39
-
40
- if (!sql) return query.apply(this, arguments)
66
+ const resolvedSql = resolveSqlString(sql)
67
+ if (resolvedSql === undefined) return query.apply(this, arguments)
41
68
 
42
69
  const abortController = new AbortController()
43
- startOuterQueryCh.publish({ sql, abortController })
70
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
44
71
 
45
72
  if (abortController.signal.aborted) {
46
73
  const addCommand = this.addCommand
@@ -56,7 +83,7 @@ function wrapConnection (Connection, version) {
56
83
  cb = queryCommand.onResult
57
84
 
58
85
  process.nextTick(() => {
59
- if (cb) {
86
+ if (typeof cb === 'function') {
60
87
  cb(abortController.signal.reason)
61
88
  } else {
62
89
  queryCommand.emit('error', abortController.signal.reason)
@@ -76,12 +103,11 @@ function wrapConnection (Connection, version) {
76
103
  shimmer.wrap(Connection.prototype, 'execute', execute => function (sql, values, cb) {
77
104
  if (!startOuterQueryCh.hasSubscribers) return execute.apply(this, arguments)
78
105
 
79
- if (sql !== null && typeof sql === 'object') sql = sql.sql
80
-
81
- if (!sql) return execute.apply(this, arguments)
106
+ const resolvedSql = resolveSqlString(sql)
107
+ if (resolvedSql === undefined) return execute.apply(this, arguments)
82
108
 
83
109
  const abortController = new AbortController()
84
- startOuterQueryCh.publish({ sql, abortController })
110
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
85
111
 
86
112
  if (abortController.signal.aborted) {
87
113
  const addCommand = this.addCommand
@@ -94,7 +120,9 @@ function wrapConnection (Connection, version) {
94
120
  this.addCommand = addCommand
95
121
  }
96
122
 
97
- result?.onResult(abortController.signal.reason)
123
+ if (typeof result?.onResult === 'function') {
124
+ result.onResult(abortController.signal.reason)
125
+ }
98
126
 
99
127
  return result
100
128
  }
@@ -104,33 +132,48 @@ function wrapConnection (Connection, version) {
104
132
 
105
133
  return Connection
106
134
 
107
- function bindExecute (execute, ctx) {
108
- return shimmer.wrapFunction(execute, execute => function executeWithTrace (packet, connection) {
109
- const onResult = this.onResult
135
+ /**
136
+ * @param {object} cmd
137
+ * @param {object} ctx
138
+ * @returns {void}
139
+ */
140
+ function wrapCommandOnResult (cmd, ctx) {
141
+ const onResult = cmd?.onResult
142
+ if (typeof onResult !== 'function') return
110
143
 
111
- if (onResult) {
112
- this.onResult = function () {
113
- return commandFinishCh.runStores(ctx, onResult, this, ...arguments)
114
- }
115
- }
144
+ const cached = wrappedOnResult.get(cmd)
116
145
 
117
- return commandStartCh.runStores(ctx, execute, this, ...arguments)
118
- })
146
+ if (cached === onResult) return
147
+
148
+ const wrapped = function () {
149
+ return commandFinishCh.runStores(ctx, onResult, this, ...arguments)
150
+ }
151
+
152
+ wrappedOnResult.set(cmd, wrapped)
153
+ cmd.onResult = wrapped
119
154
  }
120
155
 
156
+ /**
157
+ * @param {object} cmd
158
+ * @param {Function} execute
159
+ * @param {object} ctx
160
+ * @param {object} config
161
+ * @returns {Function}
162
+ */
121
163
  function wrapExecute (cmd, execute, ctx, config) {
122
164
  return shimmer.wrapFunction(execute, execute => function executeWithTrace (packet, connection) {
123
- ctx.sql = cmd.statement ? cmd.statement.query : cmd.sql
165
+ const command = /** @type {{ statement?: { query?: unknown }, sql?: unknown }} */ (cmd)
166
+ ctx.sql = command.statement ? command.statement.query : command.sql
124
167
  ctx.conf = config
125
168
 
126
169
  return startCh.runStores(ctx, () => {
127
- if (cmd.statement) {
128
- cmd.statement.query = ctx.sql
170
+ if (command.statement) {
171
+ command.statement.query = ctx.sql
129
172
  } else {
130
- cmd.sql = ctx.sql
173
+ command.sql = ctx.sql
131
174
  }
132
175
 
133
- if (this.onResult) {
176
+ if (typeof this.onResult === 'function') {
134
177
  const onResult = this.onResult
135
178
 
136
179
  this.onResult = shimmer.wrapFunction(onResult, onResult => function (error) {
@@ -141,11 +184,14 @@ function wrapConnection (Connection, version) {
141
184
  finishCh.runStores(ctx, onResult, this, ...arguments)
142
185
  })
143
186
  } else {
144
- this.once(errorMonitor, error => {
145
- ctx.error = error
146
- errorCh.publish(ctx)
147
- })
148
- this.once('end', () => finishCh.publish(ctx))
187
+ const command = /** @type {{ once?: Function }} */ (this)
188
+ if (typeof command.once === 'function') {
189
+ command.once(errorMonitor, error => {
190
+ ctx.error = error
191
+ errorCh.publish(ctx)
192
+ })
193
+ command.once('end', () => finishCh.publish(ctx))
194
+ }
149
195
  }
150
196
 
151
197
  this.execute = execute
@@ -157,9 +203,14 @@ function wrapConnection (Connection, version) {
157
203
  errorCh.publish(ctx)
158
204
  }
159
205
  })
160
- }, cmd)
206
+ })
161
207
  }
162
208
  }
209
+ /**
210
+ * @param {Function} Pool
211
+ * @param {string} version
212
+ * @returns {Function}
213
+ */
163
214
  function wrapPool (Pool, version) {
164
215
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
165
216
  const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
@@ -167,12 +218,11 @@ function wrapPool (Pool, version) {
167
218
  shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
168
219
  if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
169
220
 
170
- if (sql !== null && typeof sql === 'object') sql = sql.sql
171
-
172
- if (!sql) return query.apply(this, arguments)
221
+ const resolvedSql = resolveSqlString(sql)
222
+ if (resolvedSql === undefined) return query.apply(this, arguments)
173
223
 
174
224
  const abortController = new AbortController()
175
- startOuterQueryCh.publish({ sql, abortController })
225
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
176
226
 
177
227
  if (abortController.signal.aborted) {
178
228
  const getConnection = this.getConnection
@@ -206,21 +256,22 @@ function wrapPool (Pool, version) {
206
256
  shimmer.wrap(Pool.prototype, 'execute', execute => function (sql, values, cb) {
207
257
  if (!startOuterQueryCh.hasSubscribers) return execute.apply(this, arguments)
208
258
 
209
- if (sql !== null && typeof sql === 'object') sql = sql.sql
210
-
211
- if (!sql) return execute.apply(this, arguments)
259
+ const resolvedSql = resolveSqlString(sql)
260
+ if (resolvedSql === undefined) return execute.apply(this, arguments)
212
261
 
213
262
  const abortController = new AbortController()
214
- startOuterQueryCh.publish({ sql, abortController })
263
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
215
264
 
216
265
  if (abortController.signal.aborted) {
217
266
  if (typeof values === 'function') {
218
267
  cb = values
219
268
  }
220
269
 
221
- process.nextTick(() => {
222
- cb(abortController.signal.reason)
223
- })
270
+ if (typeof cb === 'function') {
271
+ process.nextTick(() => {
272
+ /** @type {Function} */ (cb)(abortController.signal.reason)
273
+ })
274
+ }
224
275
  return
225
276
  }
226
277
 
@@ -230,6 +281,10 @@ function wrapPool (Pool, version) {
230
281
  return Pool
231
282
  }
232
283
 
284
+ /**
285
+ * @param {Function} PoolCluster
286
+ * @returns {Function}
287
+ */
233
288
  function wrapPoolCluster (PoolCluster) {
234
289
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
235
290
  const wrappedPoolNamespaces = new WeakSet()
@@ -239,12 +294,11 @@ function wrapPoolCluster (PoolCluster) {
239
294
 
240
295
  if (startOuterQueryCh.hasSubscribers && !wrappedPoolNamespaces.has(poolNamespace)) {
241
296
  shimmer.wrap(poolNamespace, 'query', query => function (sql, values, cb) {
242
- if (sql !== null && typeof sql === 'object') sql = sql.sql
243
-
244
- if (!sql) return query.apply(this, arguments)
297
+ const resolvedSql = resolveSqlString(sql)
298
+ if (resolvedSql === undefined) return query.apply(this, arguments)
245
299
 
246
300
  const abortController = new AbortController()
247
- startOuterQueryCh.publish({ sql, abortController })
301
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
248
302
 
249
303
  if (abortController.signal.aborted) {
250
304
  const getConnection = this.getConnection
@@ -274,21 +328,22 @@ function wrapPoolCluster (PoolCluster) {
274
328
  })
275
329
 
276
330
  shimmer.wrap(poolNamespace, 'execute', execute => function (sql, values, cb) {
277
- if (sql !== null && typeof sql === 'object') sql = sql.sql
278
-
279
- if (!sql) return execute.apply(this, arguments)
331
+ const resolvedSql = resolveSqlString(sql)
332
+ if (resolvedSql === undefined) return execute.apply(this, arguments)
280
333
 
281
334
  const abortController = new AbortController()
282
- startOuterQueryCh.publish({ sql, abortController })
335
+ startOuterQueryCh.publish({ sql: resolvedSql, abortController })
283
336
 
284
337
  if (abortController.signal.aborted) {
285
338
  if (typeof values === 'function') {
286
339
  cb = values
287
340
  }
288
341
 
289
- process.nextTick(() => {
290
- cb(abortController.signal.reason)
291
- })
342
+ if (typeof cb === 'function') {
343
+ process.nextTick(() => {
344
+ /** @type {Function} */ (cb)(abortController.signal.reason)
345
+ })
346
+ }
292
347
 
293
348
  return
294
349
  }
@@ -305,9 +360,21 @@ function wrapPoolCluster (PoolCluster) {
305
360
  return PoolCluster
306
361
  }
307
362
 
308
- addHook({ name: 'mysql2', file: 'lib/base/connection.js', versions: ['>=3.11.5'] }, wrapConnection)
309
- addHook({ name: 'mysql2', file: 'lib/connection.js', versions: ['1 - 3.11.4'] }, wrapConnection)
310
- addHook({ name: 'mysql2', file: 'lib/pool.js', versions: ['1 - 3.11.4'] }, wrapPool)
363
+ addHook(
364
+ { name: 'mysql2', file: 'lib/base/connection.js', versions: ['>=3.11.5'] },
365
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapConnection)
366
+ )
367
+ addHook(
368
+ { name: 'mysql2', file: 'lib/connection.js', versions: ['1 - 3.11.4'] },
369
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapConnection)
370
+ )
371
+ addHook(
372
+ { name: 'mysql2', file: 'lib/pool.js', versions: ['1 - 3.11.4'] },
373
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapPool)
374
+ )
311
375
 
312
376
  // PoolNamespace.prototype.query does not exist in mysql2<2.3.0
313
- addHook({ name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['2.3.0 - 3.11.4'] }, wrapPoolCluster)
377
+ addHook(
378
+ { name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['2.3.0 - 3.11.4'] },
379
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapPoolCluster)
380
+ )
@@ -396,6 +396,14 @@ function testEndHandler ({
396
396
  }
397
397
  }
398
398
 
399
+ // Check if all EFD retries failed
400
+ if (testStatuses.length === earlyFlakeDetectionNumRetries + 1 &&
401
+ (test._ddIsNew || test._ddIsModified) &&
402
+ test._ddIsEfdRetry &&
403
+ testStatuses.every(status => status === 'fail')) {
404
+ test._ddHasFailedAllRetries = true
405
+ }
406
+
399
407
  // this handles tests that do not go through the worker process (because they're skipped)
400
408
  if (shouldCreateTestSpan) {
401
409
  const testResult = results.at(-1)
@@ -0,0 +1,92 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook } = require('./helpers/instrument')
5
+
6
+ const checkoutSessionCreateFinishCh = channel('datadog:stripe:checkoutSession:create:finish')
7
+ const paymentIntentCreateFinishCh = channel('datadog:stripe:paymentIntent:create:finish')
8
+ const constructEventFinishCh = channel('datadog:stripe:constructEvent:finish')
9
+
10
+ function wrapSessionCreate (create) {
11
+ return function wrappedSessionCreate () {
12
+ const promise = create.apply(this, arguments)
13
+
14
+ if (!checkoutSessionCreateFinishCh.hasSubscribers) return promise
15
+
16
+ return promise.then((result) => {
17
+ checkoutSessionCreateFinishCh.publish(result)
18
+ return result
19
+ })
20
+ }
21
+ }
22
+
23
+ function wrapPaymentIntentCreate (create) {
24
+ return function wrappedPaymentIntentCreate () {
25
+ const promise = create.apply(this, arguments)
26
+
27
+ if (!paymentIntentCreateFinishCh.hasSubscribers) return promise
28
+
29
+ return promise.then((result) => {
30
+ paymentIntentCreateFinishCh.publish(result)
31
+ return result
32
+ })
33
+ }
34
+ }
35
+
36
+ function wrapConstructEvent (constructEvent) {
37
+ return function wrappedConstructEvent () {
38
+ const result = constructEvent.apply(this, arguments)
39
+
40
+ // no need to check for hasSubscribers,
41
+ // if it's false, the publish function will be noop
42
+ constructEventFinishCh.publish(result)
43
+
44
+ return result
45
+ }
46
+ }
47
+
48
+ function wrapConstructEventAsync (constructEventAsync) {
49
+ return function wrappedConstructEventAsync () {
50
+ const promise = constructEventAsync.apply(this, arguments)
51
+
52
+ if (!constructEventFinishCh.hasSubscribers) return promise
53
+
54
+ return promise.then((result) => {
55
+ constructEventFinishCh.publish(result)
56
+ return result
57
+ })
58
+ }
59
+ }
60
+
61
+ function wrapStripe (Stripe) {
62
+ return function wrappedStripe () {
63
+ let stripe = Stripe.apply(this, arguments)
64
+
65
+ // to support both with and without "new" operator syntax
66
+ if (this instanceof Stripe) {
67
+ stripe = this
68
+ }
69
+
70
+ if (typeof stripe.checkout?.sessions?.create === 'function') {
71
+ shimmer.wrap(stripe.checkout.sessions, 'create', wrapSessionCreate)
72
+ }
73
+ if (typeof stripe.paymentIntents?.create === 'function') {
74
+ shimmer.wrap(stripe.paymentIntents, 'create', wrapPaymentIntentCreate)
75
+ }
76
+ if (typeof stripe.webhooks?.constructEvent === 'function') {
77
+ shimmer.wrap(stripe.webhooks, 'constructEvent', wrapConstructEvent)
78
+ }
79
+ if (typeof stripe.webhooks?.constructEventAsync === 'function') {
80
+ shimmer.wrap(stripe.webhooks, 'constructEventAsync', wrapConstructEventAsync)
81
+ }
82
+
83
+ return stripe
84
+ }
85
+ }
86
+
87
+ addHook({
88
+ name: 'stripe',
89
+ versions: ['9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '>=20.0.0'],
90
+ }, Stripe => {
91
+ return shimmer.wrapFunction(Stripe, wrapStripe)
92
+ })
@@ -1036,6 +1036,17 @@ addHook({
1036
1036
  }
1037
1037
  }
1038
1038
 
1039
+ // Check if all EFD retries failed
1040
+ const providedContext = getProvidedContext()
1041
+ if (providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))) {
1042
+ const statuses = taskToStatuses.get(task)
1043
+ // statuses only includes repetitions (not the initial run), so we check against numRepeats (not +1)
1044
+ if (statuses && statuses.length === providedContext.numRepeats &&
1045
+ statuses.every(status => status === 'fail')) {
1046
+ hasFailedAllRetries = true
1047
+ }
1048
+ }
1049
+
1039
1050
  if (testCtx) {
1040
1051
  const isRetry = task.result?.retryCount > 0
1041
1052
  // `duration` is the duration of all the retries, so it can't be used if there are retries
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
- const serverless = require('../../dd-trace/src/plugins/util/serverless')
5
4
  const web = require('../../dd-trace/src/plugins/util/web')
6
5
 
7
6
  const triggerMap = {
@@ -26,19 +25,51 @@ class AzureFunctionsPlugin extends TracingPlugin {
26
25
  bindStart (ctx) {
27
26
  const meta = getMetaForTrigger(ctx)
28
27
  const triggerType = triggerMap[ctx.methodName]
28
+ const isHttpTrigger = triggerType === 'Http'
29
29
  const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
30
- const childOf = isMessagingService ? null : extractTraceContext(this._tracer, ctx)
31
- const span = this.startSpan(this.operationName(), {
32
- childOf,
33
- service: this.serviceName(),
34
- type: 'serverless',
35
- meta,
36
- }, ctx)
37
-
38
- if (isMessagingService) {
39
- setSpanLinks(triggerType, this.tracer, span, ctx)
40
- }
41
30
 
31
+ let span
32
+
33
+ if (isHttpTrigger) {
34
+ const { httpRequest } = ctx
35
+ const path = (new URL(httpRequest.url)).pathname
36
+ const req = {
37
+ method: httpRequest.method,
38
+ headers: Object.fromEntries(httpRequest.headers),
39
+ url: path,
40
+ }
41
+ // Patch the request to create web context
42
+ const webContext = web.patch(req)
43
+ webContext.config = this.config
44
+ webContext.tracer = this.tracer
45
+ webContext.paths = [path]
46
+ // Creates a standard span and an inferred proxy span if headers are present
47
+ span = web.startServerlessSpanWithInferredProxy(
48
+ this.tracer,
49
+ this.config,
50
+ this.operationName(),
51
+ req,
52
+ ctx
53
+ )
54
+
55
+ span._integrationName = 'azure-functions'
56
+ span.context()._tags.component = 'azure-functions'
57
+ span.addTags(meta)
58
+ webContext.span = span
59
+ webContext.azureFunctionCtx = ctx
60
+ ctx.webContext = webContext
61
+ } else {
62
+ // For non-HTTP triggers, use standard flow
63
+ span = this.startSpan(this.operationName(), {
64
+ service: this.serviceName(),
65
+ type: 'serverless',
66
+ meta,
67
+ }, ctx)
68
+
69
+ if (isMessagingService) {
70
+ setSpanLinks(triggerType, this.tracer, span, ctx)
71
+ }
72
+ }
42
73
  ctx.span = span
43
74
  return ctx.currentStore
44
75
  }
@@ -48,24 +79,16 @@ class AzureFunctionsPlugin extends TracingPlugin {
48
79
  ctx.currentStore.span.setTag('error.message', ctx.error)
49
80
  }
50
81
 
51
- asyncEnd (ctx) {
52
- const { httpRequest, methodName, result = {} } = ctx
53
- if (triggerMap[methodName] === 'Http') {
54
- // If the method is an HTTP trigger, we need to patch the request and finish the span
55
- const path = (new URL(httpRequest.url)).pathname
56
- const req = {
57
- method: httpRequest.method,
58
- headers: Object.fromEntries(httpRequest.headers),
59
- url: path,
82
+ asyncStart (ctx) {
83
+ const { methodName, result = {}, webContext } = ctx
84
+ const triggerType = triggerMap[methodName]
85
+
86
+ // For HTTP triggers, use web utilities to finish all spans (including inferred proxy)
87
+ if (triggerType === 'Http') {
88
+ if (webContext) {
89
+ webContext.res = { statusCode: result.status }
90
+ web.finishAll(webContext, 'serverless')
60
91
  }
61
- const context = web.patch(req)
62
- context.config = this.config
63
- context.paths = [path]
64
- context.res = { statusCode: result.status }
65
- context.span = ctx.currentStore.span
66
-
67
- serverless.finishSpan(context)
68
- // Fallback for other trigger types
69
92
  } else {
70
93
  super.finish()
71
94
  }
@@ -80,6 +103,7 @@ function getMetaForTrigger ({ functionName, methodName, invocationContext }) {
80
103
  let meta = {
81
104
  'aas.function.name': functionName,
82
105
  'aas.function.trigger': mapTriggerTag(methodName),
106
+ 'span.type': 'serverless',
83
107
  }
84
108
 
85
109
  if (triggerMap[methodName] === 'ServiceBus') {
@@ -112,14 +136,6 @@ function mapTriggerTag (methodName) {
112
136
  return triggerMap[methodName] || 'Unknown'
113
137
  }
114
138
 
115
- function extractTraceContext (tracer, ctx) {
116
- if (triggerMap[ctx.methodName] === 'Http') {
117
- return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
118
- }
119
- // Returning null indicates that the span is a root span
120
- return null
121
- }
122
-
123
139
  // message & messages & batch with cardinality of 1 == applicationProperties
124
140
  // messages with cardinality of many == applicationPropertiesArray
125
141
  function setSpanLinks (triggerType, tracer, span, ctx) {
@@ -937,6 +937,13 @@ class CypressPlugin {
937
937
  this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
938
938
  }
939
939
  }
940
+ // Check if all EFD retries failed
941
+ if ((isNew || isModified) && this.earlyFlakeDetectionNumRetries > 0) {
942
+ const isLastEfdAttempt = testStatuses.length === this.earlyFlakeDetectionNumRetries + 1
943
+ if (isLastEfdAttempt && testStatuses.every(status => status === 'fail')) {
944
+ this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
945
+ }
946
+ }
940
947
  if (isAttemptToFix) {
941
948
  this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
942
949
  if (testStatuses.length > 1) {
@@ -27,6 +27,12 @@ module.exports = {
27
27
  WAF_CONTEXT_PROCESSOR: 'waf.context.processor',
28
28
 
29
29
  HTTP_OUTGOING_URL: 'server.io.net.url',
30
+ HTTP_OUTGOING_METHOD: 'server.io.net.request.method',
31
+ HTTP_OUTGOING_HEADERS: 'server.io.net.request.headers',
32
+ HTTP_OUTGOING_RESPONSE_STATUS: 'server.io.net.response.status',
33
+ HTTP_OUTGOING_RESPONSE_HEADERS: 'server.io.net.response.headers',
34
+ HTTP_OUTGOING_RESPONSE_BODY: 'server.io.net.response.body',
35
+
30
36
  FS_OPERATION_PATH: 'server.io.fs.file',
31
37
 
32
38
  DB_STATEMENT: 'server.db.statement',
@@ -37,4 +43,9 @@ module.exports = {
37
43
 
38
44
  LOGIN_SUCCESS: 'server.business_logic.users.login.success',
39
45
  LOGIN_FAILURE: 'server.business_logic.users.login.failure',
46
+
47
+ PAYMENT_CREATION: 'server.business_logic.payment.creation',
48
+ PAYMENT_SUCCESS: 'server.business_logic.payment.success',
49
+ PAYMENT_FAILURE: 'server.business_logic.payment.failure',
50
+ PAYMENT_CANCELLATION: 'server.business_logic.payment.cancellation',
40
51
  }
@@ -23,6 +23,7 @@ module.exports = {
23
23
  fsOperationStart: dc.channel('apm:fs:operation:start'),
24
24
  graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
25
25
  httpClientRequestStart: dc.channel('apm:http:client:request:start'),
26
+ httpClientResponseFinish: dc.channel('apm:http:client:response:finish'),
26
27
  incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
27
28
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
28
29
  multerParser: dc.channel('datadog:multer:read:finish'),
@@ -37,10 +38,13 @@ module.exports = {
37
38
  responseBody: dc.channel('datadog:express:response:json:start'),
38
39
  responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
39
40
  responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
40
- routerParam: dc.channel('datadog:router:param:start'),
41
41
  routerMiddlewareError: dc.channel('apm:router:middleware:error'),
42
+ routerParam: dc.channel('datadog:router:param:start'),
42
43
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
43
44
  setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
44
45
  startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
46
+ stripeCheckoutSessionCreate: dc.channel('datadog:stripe:checkoutSession:create:finish'),
47
+ stripeConstructEvent: dc.channel('datadog:stripe:constructEvent:finish'),
48
+ stripePaymentIntentCreate: dc.channel('datadog:stripe:paymentIntent:create:finish'),
45
49
  wafRunFinished: dc.channel('datadog:waf:run:finish'),
46
50
  }