dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -24,6 +24,40 @@ const suppressedTestFailures = new Map()
24
24
  // to a cross-origin URL, safeGetRum() handles the access error.
25
25
  let originalWindow
26
26
 
27
+ let currentTestCommands = []
28
+ const commandStartTimes = new Map()
29
+ const INTERNAL_CYPRESS_COMMANDS = new Set(['wrap', 'then', 'noop'])
30
+
31
+ Cypress.on('command:start', (command) => {
32
+ commandStartTimes.set(command.get('id'), { startTime: Date.now(), name: command.get('name') })
33
+ })
34
+
35
+ Cypress.on('command:end', (command) => {
36
+ const id = command.get('id')
37
+ const entry = commandStartTimes.get(id)
38
+ commandStartTimes.delete(id)
39
+
40
+ const name = command.get('name')
41
+ const args = command.get('args')
42
+ if (name === 'task' && args && typeof args[0] === 'string' && args[0].startsWith('dd:')) {
43
+ return
44
+ }
45
+ if (INTERNAL_CYPRESS_COMMANDS.has(name)) {
46
+ return
47
+ }
48
+ if (entry == null) {
49
+ return
50
+ }
51
+ const err = command.get('err')
52
+ currentTestCommands.push({
53
+ name,
54
+ startTime: entry.startTime,
55
+ endTime: Date.now(),
56
+ // Serialize the error to a plain object so it survives cy.task JSON transport.
57
+ error: err ? { message: err.message, stack: err.stack, name: err.name } : null,
58
+ })
59
+ })
60
+
27
61
  // If the test is using multi domain with cy.origin, trying to access
28
62
  // window properties will result in a cross origin error.
29
63
  function safeGetRum (window) {
@@ -56,6 +90,29 @@ function getTestProperties (testName) {
56
90
  // By not re-throwing the error, Cypress marks the test as passed
57
91
  // This allows quarantined tests to run but not affect the exit code
58
92
  Cypress.on('fail', (err, runnable) => {
93
+ // For commands that time out, command:end may never fire.
94
+ // Finalize any in-flight commands so their step spans carry the error.
95
+ const hadInFlightCommands = commandStartTimes.size > 0
96
+ for (const [, { startTime, name }] of commandStartTimes) {
97
+ if (INTERNAL_CYPRESS_COMMANDS.has(name)) continue
98
+ currentTestCommands.push({
99
+ name,
100
+ startTime,
101
+ endTime: Date.now(),
102
+ error: { message: err.message, stack: err.stack, name: err.name },
103
+ })
104
+ }
105
+ commandStartTimes.clear()
106
+
107
+ // If command:end fired for all commands (none in-flight) but the last command
108
+ // has no error, it means command:end fired before the error was attached to it.
109
+ if (!hadInFlightCommands && currentTestCommands.length > 0) {
110
+ const lastCommand = currentTestCommands[currentTestCommands.length - 1]
111
+ if (!lastCommand.error) {
112
+ lastCommand.error = { message: err.message, stack: err.stack, name: err.name }
113
+ }
114
+ }
115
+
59
116
  if (!isTestManagementEnabled) {
60
117
  throw err
61
118
  }
@@ -169,6 +226,9 @@ beforeEach(function () {
169
226
  retryReasonsByTestName.delete(testName)
170
227
  }
171
228
 
229
+ currentTestCommands = []
230
+ commandStartTimes.clear()
231
+
172
232
  cy.on('window:load', (win) => {
173
233
  originalWindow = win
174
234
  })
@@ -212,6 +272,11 @@ beforeEach(function () {
212
272
  if (shouldSkip) {
213
273
  this.skip()
214
274
  }
275
+ }).then(() => {
276
+ // Clear any commands accumulated during DD-owned setup (e.g. setCookie, RUM restart)
277
+ // so they are not reported as user test steps.
278
+ currentTestCommands = []
279
+ commandStartTimes.clear()
215
280
  })
216
281
  })
217
282
 
@@ -289,6 +354,9 @@ afterEach(function () {
289
354
  testInfo.testSourceStack = invocationDetails.stack
290
355
  } catch {}
291
356
 
357
+ // Snapshot before any DD-owned Cypress commands so they are not reported as test steps.
358
+ const commandsToReport = [...currentTestCommands]
359
+
292
360
  const rum = safeGetRum(originalWindow)
293
361
  if (rum) {
294
362
  testInfo.isRUMActive = true
@@ -310,5 +378,5 @@ afterEach(function () {
310
378
  suppressedTestFailures.delete(testName)
311
379
  }
312
380
 
313
- cy.task('dd:afterEach', { test: testInfo, coverage })
381
+ cy.task('dd:afterEach', { test: testInfo, coverage, commands: commandsToReport })
314
382
  })
@@ -23,22 +23,24 @@ class DNSLookupPlugin extends ClientPlugin {
23
23
  return ctx.currentStore
24
24
  }
25
25
 
26
- bindFinish (ctx) {
26
+ finish (ctx) {
27
27
  const span = ctx.currentStore.span
28
28
  const result = ctx.result
29
29
 
30
30
  if (Array.isArray(result)) {
31
- const addresses = Array.isArray(result)
32
- ? result.map(address => address.address).sort()
33
- : [result]
34
-
31
+ // `lookup(..., { all: true })` or `dns.promises.lookup(..., { all: true })`.
32
+ const addresses = result.map(entry => entry.address).sort()
35
33
  span.setTag('dns.address', addresses[0])
36
34
  span.setTag('dns.addresses', addresses.join(','))
35
+ } else if (result && typeof result === 'object') {
36
+ // `dns.promises.lookup(...)` resolves to `{ address, family }`; the callback variant
37
+ // passes the address as a string.
38
+ span.setTag('dns.address', result.address)
37
39
  } else {
38
40
  span.setTag('dns.address', result)
39
41
  }
40
42
 
41
- return ctx.parentStore
43
+ super.finish(ctx)
42
44
  }
43
45
  }
44
46
 
@@ -5,23 +5,43 @@ const DatabasePlugin = require('../../dd-trace/src/plugins/database')
5
5
  class ElasticsearchPlugin extends DatabasePlugin {
6
6
  static id = 'elasticsearch'
7
7
 
8
+ #urlTag
9
+ #methodTag
10
+ #bodyTag
11
+ #paramsTag
12
+
13
+ constructor (...args) {
14
+ super(...args)
15
+
16
+ // Per-instance because `system` differs on the OpenSearchPlugin subclass.
17
+ const { system } = this
18
+ this.#urlTag = `${system}.url`
19
+ this.#methodTag = `${system}.method`
20
+ this.#bodyTag = `${system}.body`
21
+ this.#paramsTag = `${system}.params`
22
+ }
23
+
8
24
  bindStart (ctx) {
9
25
  const { params } = ctx
10
26
 
11
- const body = getBody(params.body || params.bulkBody)
27
+ const meta = {
28
+ 'db.type': this.system,
29
+ [this.#urlTag]: params.path,
30
+ [this.#methodTag]: params.method,
31
+ [this.#bodyTag]: getBody(params.body || params.bulkBody),
32
+ }
33
+
34
+ const queryString = params.querystring || params.query
35
+ if (queryString) {
36
+ meta[this.#paramsTag] = JSON.stringify(queryString)
37
+ }
12
38
 
13
39
  this.startSpan(this.operationName(), {
14
40
  service: this.serviceName({ pluginConfig: this.config }),
15
41
  resource: `${params.method} ${quantizePath(params.path)}`,
16
42
  type: 'elasticsearch',
17
43
  kind: 'client',
18
- meta: {
19
- 'db.type': this.system,
20
- [`${this.system}.url`]: params.path,
21
- [`${this.system}.method`]: params.method,
22
- [`${this.system}.body`]: body,
23
- [`${this.system}.params`]: JSON.stringify(params.querystring || params.query),
24
- },
44
+ meta,
25
45
  }, ctx)
26
46
 
27
47
  return ctx.currentStore
@@ -181,7 +181,7 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
181
181
 
182
182
  if (linkContext) {
183
183
  if (span.addLink) {
184
- span.addLink(linkContext, {})
184
+ span.addLink({ context: linkContext, attributes: {} })
185
185
  } else {
186
186
  span._links ??= []
187
187
  span._links.push({ context: linkContext, attributes: {} })
@@ -17,6 +17,8 @@ class GraphQLExecutePlugin extends TracingPlugin {
17
17
  const document = args.document
18
18
  const source = this.config.source && document && docSource
19
19
 
20
+ ctx.collapse = this.config.collapse
21
+
20
22
  const span = this.startSpan(this.operationName(), {
21
23
  service: this.config.service || this.serviceName(),
22
24
  resource: getSignature(document, name, type, this.config.signature),
@@ -3,59 +3,61 @@
3
3
  const dc = require('dc-polyfill')
4
4
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
5
 
6
- const collapsedPathSym = Symbol('collapsedPaths')
7
-
8
6
  class GraphQLResolvePlugin extends TracingPlugin {
9
7
  static id = 'graphql'
10
8
  static operation = 'resolve'
11
9
 
10
+ /**
11
+ * @param {{
12
+ * rootCtx: {
13
+ * source?: string,
14
+ * collapse: boolean,
15
+ * collapsedFields?: Map<string, { ctx: object }>,
16
+ * },
17
+ * args: Record<string, unknown>,
18
+ * path: { prev: object | undefined, key: string | number },
19
+ * pathString: string,
20
+ * fieldName: string,
21
+ * returnType: { name: string },
22
+ * fieldNode: { loc?: { start: number, end: number }, arguments?: object[], directives?: object[] } | undefined,
23
+ * variableValues: Record<string, unknown> | undefined,
24
+ * }} fieldCtx
25
+ */
12
26
  start (fieldCtx) {
13
- const { info, rootCtx, args, path: pathAsArray, pathString } = fieldCtx
14
-
15
- // we need to get the parent span to the field if it exists for correct span parenting
16
- // of nested fields
17
- const parentField = getParentField(rootCtx, pathString)
18
- const childOf = parentField?.ctx?.currentStore?.span
27
+ if (!shouldInstrument(this.config, fieldCtx.path)) return
19
28
 
20
- fieldCtx.parent = parentField
29
+ const { rootCtx, args, path, pathString, fieldName, returnType, fieldNode, variableValues } = fieldCtx
21
30
 
22
- if (!shouldInstrument(this.config, pathAsArray)) return
23
- const computedPathString = this.config.collapse
24
- ? buildCollapsedPathString(pathAsArray)
25
- : pathString
31
+ // Siblings 2..N of a collapsed list share the first sibling's span, so
32
+ // skip span creation here. updateField still fires on the shared ctx and
33
+ // advances the shared span's finishTime.
34
+ if (rootCtx.collapse && rootCtx.collapsedFields?.has(pathString)) return
26
35
 
27
- if (this.config.collapse) {
28
- if (rootCtx.fields[computedPathString]) return
29
-
30
- if (!rootCtx[collapsedPathSym]) {
31
- rootCtx[collapsedPathSym] = Object.create(null)
32
- } else if (rootCtx[collapsedPathSym][computedPathString]) {
33
- return
34
- }
35
-
36
- rootCtx[collapsedPathSym][computedPathString] = true
37
- }
36
+ const parentField = getParentField(rootCtx, path)
37
+ const childOf = parentField?.ctx?.currentStore?.span
38
38
 
39
39
  const document = rootCtx.source
40
- const fieldNode = info.fieldNodes[0]
41
40
  const loc = this.config.source && document && fieldNode && fieldNode.loc
42
41
  const source = loc && document.slice(loc.start, loc.end)
43
42
 
43
+ let namedReturnType = returnType
44
+ while (namedReturnType.ofType) namedReturnType = namedReturnType.ofType
45
+
44
46
  const span = this.startSpan('graphql.resolve', {
45
47
  service: this.config.service,
46
- resource: `${info.fieldName}:${info.returnType}`,
48
+ resource: `${fieldName}:${returnType}`,
47
49
  childOf,
48
50
  type: 'graphql',
49
51
  meta: {
50
- 'graphql.field.name': info.fieldName,
51
- 'graphql.field.path': computedPathString,
52
- 'graphql.field.type': info.returnType.name,
52
+ 'graphql.field.name': fieldName,
53
+ 'graphql.field.path': pathString,
54
+ 'graphql.field.type': namedReturnType.name,
53
55
  'graphql.source': source,
54
56
  },
55
57
  }, fieldCtx)
56
58
 
57
59
  if (fieldNode && this.config.variables && fieldNode.arguments) {
58
- const variables = this.config.variables(info.variableValues)
60
+ const variables = this.config.variables(variableValues)
59
61
 
60
62
  for (const arg of fieldNode.arguments) {
61
63
  if (arg.value?.name && arg.value.kind === 'Variable' && variables[arg.value.name.value]) {
@@ -66,7 +68,7 @@ class GraphQLResolvePlugin extends TracingPlugin {
66
68
  }
67
69
 
68
70
  if (this.resolverStartCh.hasSubscribers) {
69
- this.resolverStartCh.publish({ ctx: rootCtx, resolverInfo: getResolverInfo(info, args) })
71
+ this.resolverStartCh.publish({ ctx: rootCtx, resolverInfo: getResolverInfo(fieldNode, fieldName, args) })
70
72
  }
71
73
 
72
74
  return fieldCtx.currentStore
@@ -76,11 +78,11 @@ class GraphQLResolvePlugin extends TracingPlugin {
76
78
  super(...args)
77
79
 
78
80
  this.addTraceSub('updateField', (ctx) => {
79
- const { field, error, path: pathAsArray } = ctx
81
+ // start short-circuited on the depth gate, so there is no span to advance.
82
+ if (ctx.currentStore === undefined) return
80
83
 
81
- if (!shouldInstrument(this.config, pathAsArray)) return
82
-
83
- const span = ctx?.currentStore?.span || this.activeSpan
84
+ const { field, error } = ctx
85
+ const span = ctx.currentStore.span
84
86
  field.finishTime = span._getTime ? span._getTime() : 0
85
87
  field.error = field.error || error
86
88
  })
@@ -105,38 +107,38 @@ class GraphQLResolvePlugin extends TracingPlugin {
105
107
 
106
108
  // helpers
107
109
 
108
- function shouldInstrument (config, pathAsArray) {
109
- if (config.depth < 0) return true
110
+ /**
111
+ * @param {{ depth: number, collapse: boolean }} config
112
+ * @param {{ prev: object | undefined, key: string | number }} path
113
+ */
114
+ function shouldInstrument (config, path) {
115
+ const depth = config.depth
116
+ if (depth < 0) return true
110
117
 
111
- let depth = 0
118
+ let count = 0
112
119
  if (config.collapse) {
113
- depth = pathAsArray.length
120
+ for (let curr = path; curr; curr = curr.prev) count += 1
114
121
  } else {
115
- for (const segment of pathAsArray) {
116
- if (typeof segment === 'string') depth += 1
122
+ for (let curr = path; curr; curr = curr.prev) {
123
+ if (typeof curr.key === 'string') count += 1
117
124
  }
118
125
  }
119
-
120
- return config.depth >= depth
121
- }
122
-
123
- function buildCollapsedPathString (pathAsArray) {
124
- let result = ''
125
- for (const segment of pathAsArray) {
126
- if (result.length > 0) result += '.'
127
- result += typeof segment === 'number' ? '*' : segment
128
- }
129
- return result
126
+ return depth >= count
130
127
  }
131
128
 
132
- function getResolverInfo (info, args) {
129
+ /**
130
+ * @param {object | undefined} fieldNode
131
+ * @param {string} fieldName
132
+ * @param {Record<string, unknown> | undefined} args
133
+ */
134
+ function getResolverInfo (fieldNode, fieldName, args) {
133
135
  let resolverVars
134
136
 
135
137
  if (args && Object.keys(args).length > 0) {
136
138
  resolverVars = { ...args }
137
139
  }
138
140
 
139
- const directives = info.fieldNodes?.[0]?.directives
141
+ const directives = fieldNode?.directives
140
142
  if (Array.isArray(directives)) {
141
143
  for (const directive of directives) {
142
144
  if (directive.arguments.length === 0) continue
@@ -151,23 +153,18 @@ function getResolverInfo (info, args) {
151
153
  }
152
154
  }
153
155
 
154
- return resolverVars === undefined ? null : { [info.fieldName]: resolverVars }
156
+ return resolverVars === undefined ? null : { [fieldName]: resolverVars }
155
157
  }
156
158
 
157
- function getParentField (parentCtx, pathToString) {
158
- let current = pathToString
159
-
160
- while (current) {
161
- const lastJoin = current.lastIndexOf('.')
162
- if (lastJoin === -1) break
163
-
164
- current = current.slice(0, lastJoin)
165
- const field = parentCtx.fields[current]
166
-
159
+ /**
160
+ * @param {{ fields: Map<object, { error: unknown, ctx: object }> }} rootCtx
161
+ * @param {{ prev: object | undefined }} path
162
+ */
163
+ function getParentField (rootCtx, path) {
164
+ for (let curr = path.prev; curr; curr = curr.prev) {
165
+ const field = rootCtx.fields.get(curr)
167
166
  if (field) return field
168
167
  }
169
-
170
- return null
171
168
  }
172
169
 
173
170
  module.exports = GraphQLResolvePlugin
@@ -7,7 +7,10 @@ function extractErrorIntoSpanEvent (config, span, exc) {
7
7
  attributes.type = exc.name
8
8
  }
9
9
 
10
- if (exc.stack) {
10
+ // graphql-js validation errors carry a lazy `.stack` accessor; reading it
11
+ // here is the only consumer in the pipeline and pays full V8 symbolisation.
12
+ const isValidationOnly = exc.locations && !exc.path && !exc.originalError?.stack
13
+ if (!isValidationOnly && exc.stack) {
11
14
  attributes.stacktrace = exc.stack
12
15
  }
13
16
 
@@ -14,30 +14,35 @@ class HttpServerPlugin extends ServerPlugin {
14
14
 
15
15
  static prefix = 'apm:http:server:request'
16
16
 
17
+ /** @type {string | undefined} */
18
+ #operationName
19
+
20
+ /** @type {object | undefined} */
21
+ #startConfig
22
+
23
+ /** @type {string | undefined} */
24
+ #serviceSource
25
+
17
26
  constructor (...args) {
18
27
  super(...args)
19
28
  this.addTraceSub('exit', message => this.exit(message))
20
29
  }
21
30
 
22
- start ({ req, res, abortController }) {
31
+ start (ctx) {
32
+ const { req, res } = ctx
23
33
  let store = legacyStorage.getStore()
24
- const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
25
- const service = this.config.service || schemaServiceName
26
- const serviceSource = (this.config.service && service !== this.tracer._service)
27
- ? 'opt.plugin'
28
- : (service === this.tracer._service ? undefined : schemaServiceSource)
34
+ if (this.#startConfig === undefined) {
35
+ this.#refreshStartCache()
36
+ }
29
37
  const span = web.startSpan(
30
38
  this.tracer,
31
- {
32
- ...this.config,
33
- service,
34
- },
39
+ this.#startConfig,
35
40
  req,
36
41
  res,
37
- this.operationName()
42
+ this.#operationName
38
43
  )
39
- if (serviceSource !== undefined) {
40
- span.setTag(SVC_SRC_KEY, serviceSource)
44
+ if (this.#serviceSource !== undefined) {
45
+ span.setTag(SVC_SRC_KEY, this.#serviceSource)
41
46
  }
42
47
  span.setTag(COMPONENT, this.constructor.id)
43
48
  span._integrationName = this.constructor.id
@@ -60,7 +65,10 @@ class HttpServerPlugin extends ServerPlugin {
60
65
  }
61
66
 
62
67
  if (appsecActive) {
63
- incomingHttpRequestStart.publish({ req, res, abortController }) // TODO: no need to make a new object here
68
+ // Reuse the ctx allocated by the HTTP server instrumentation rather
69
+ // than a fresh `{ req, res, abortController }` per request; the AppSec
70
+ // subscriber only reads from the message.
71
+ incomingHttpRequestStart.publish(ctx)
64
72
  }
65
73
  }
66
74
 
@@ -93,7 +101,24 @@ class HttpServerPlugin extends ServerPlugin {
93
101
  }
94
102
 
95
103
  configure (config) {
96
- return super.configure(web.normalizeConfig(config))
104
+ const result = super.configure(web.normalizeConfig(config))
105
+ // Invalidate the start-cache; the next `start` refills it. Resolving
106
+ // service / operation eagerly here would pin nomenclature lookups to
107
+ // the order plugins and tracer initialise.
108
+ this.#startConfig = undefined
109
+ return result
110
+ }
111
+
112
+ #refreshStartCache () {
113
+ const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
114
+ const tracerService = this.tracer._service
115
+ const configService = this.config.service
116
+ const service = configService || schemaServiceName
117
+ this.#serviceSource = (configService && service !== tracerService)
118
+ ? 'opt.plugin'
119
+ : (service === tracerService ? undefined : schemaServiceSource)
120
+ this.#operationName = this.operationName()
121
+ this.#startConfig = { ...this.config, service }
97
122
  }
98
123
  }
99
124
 
@@ -16,7 +16,6 @@ const {
16
16
  getTestSuiteCommonTags,
17
17
  addIntelligentTestRunnerSpanTags,
18
18
  TEST_PARAMETERS,
19
- TEST_COMMAND,
20
19
  TEST_FRAMEWORK_VERSION,
21
20
  TEST_SOURCE_START,
22
21
  TEST_ITR_UNSKIPPABLE,
@@ -115,7 +114,9 @@ class JestPlugin extends CiPlugin {
115
114
  isSuitesSkipped,
116
115
  isSuitesSkippingEnabled,
117
116
  isCodeCoverageEnabled,
117
+ isCoverageReportUploadEnabled,
118
118
  testCodeCoverageLinesTotal,
119
+ testSessionCoverageFiles,
119
120
  numSkippedSuites,
120
121
  hasUnskippableSuites,
121
122
  hasForcedToRunSuites,
@@ -149,6 +150,13 @@ class JestPlugin extends CiPlugin {
149
150
  }
150
151
  )
151
152
 
153
+ if (testSessionCoverageFiles?.length && isCoverageReportUploadEnabled) {
154
+ this.tracer._exporter.exportCoverage({
155
+ sessionId: this.testSessionSpan.context()._traceId,
156
+ files: testSessionCoverageFiles,
157
+ })
158
+ }
159
+
152
160
  if (isEarlyFlakeDetectionEnabled) {
153
161
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
154
162
  }
@@ -194,7 +202,7 @@ class JestPlugin extends CiPlugin {
194
202
  for (const config of configs) {
195
203
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
196
204
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
197
- config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
205
+ config._ddTestCommand = this.command
198
206
  config._ddRequestErrorTags = this.getSessionRequestErrorTags()
199
207
  config._ddItrCorrelationId = this.itrCorrelationId
200
208
  config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
@@ -596,7 +604,7 @@ class JestPlugin extends CiPlugin {
596
604
  extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
597
605
  }
598
606
  const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath) || this.testSuiteSpan
599
- const skippingEnabled = testSuiteSpan?.context()._tags?.[TEST_ITR_SKIPPING_ENABLED]
607
+ const skippingEnabled = testSuiteSpan?.context()?.getTag?.(TEST_ITR_SKIPPING_ENABLED)
600
608
  if (skippingEnabled !== undefined) {
601
609
  extraTags[TEST_ITR_SKIPPING_ENABLED] = skippingEnabled
602
610
  }
@@ -41,11 +41,16 @@ function getFormattedJestTestParameters (testParameters) {
41
41
  return formattedParameters
42
42
  }
43
43
 
44
- // Support for `@fast-check/jest`: this library modifies the test name to include the seed
45
- // A test name that keeps changing breaks some Test Optimization's features.
44
+ // @fast-check/jest appends a random seed to the reported test name. A test name that keeps changing
45
+ // breaks some Test Optimization features, so normalize this narrow suffix regardless of import style.
46
46
  const SEED_SUFFIX_RE = /\s*\(with seed=-?\d+\)\s*$/i
47
+
48
+ function removeSeedSuffixFromTestName (testName) {
49
+ return testName.replace(SEED_SUFFIX_RE, '')
50
+ }
51
+
47
52
  // https://github.com/facebook/jest/blob/3e38157ad5f23fb7d24669d24fae8ded06a7ab75/packages/jest-circus/src/utils.ts#L396
48
- function getJestTestName (test, shouldStripSeed = false) {
53
+ function getRawJestTestName (test) {
49
54
  const titles = []
50
55
  let parent = test
51
56
  do {
@@ -54,11 +59,11 @@ function getJestTestName (test, shouldStripSeed = false) {
54
59
 
55
60
  titles.shift() // remove TOP_DESCRIBE_BLOCK_NAME
56
61
 
57
- const testName = titles.join(' ')
58
- if (shouldStripSeed) {
59
- return testName.replace(SEED_SUFFIX_RE, '')
60
- }
61
- return testName
62
+ return titles.join(' ')
63
+ }
64
+
65
+ function getJestTestName (test) {
66
+ return removeSeedSuffixFromTestName(getRawJestTestName(test))
62
67
  }
63
68
 
64
69
  const globalDocblockRegExp = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/
@@ -170,6 +175,8 @@ module.exports = {
170
175
  SEED_SUFFIX_RE,
171
176
  getFormattedJestTestParameters,
172
177
  getJestTestName,
178
+ getRawJestTestName,
173
179
  getJestSuitesToRun,
174
180
  isMarkedAsUnskippable,
181
+ removeSeedSuffixFromTestName,
175
182
  }
@@ -34,7 +34,7 @@ class KafkajsBatchConsumerPlugin extends ConsumerPlugin {
34
34
  if (headers) {
35
35
  const childOf = this.tracer.extract('text_map', headers)
36
36
  if (childOf) {
37
- span.addLink(childOf)
37
+ span.addLink({ context: childOf })
38
38
  }
39
39
  }
40
40
 
@@ -91,6 +91,41 @@ class KafkajsProducerPlugin extends ProducerPlugin {
91
91
  }
92
92
  }
93
93
 
94
+ finish (ctx) {
95
+ const span = ctx?.currentStore?.span
96
+ const result = ctx?.result
97
+ if (span && Array.isArray(result) && result.length > 0) {
98
+ // The broker response is one entry per (topic, partition). Each entry
99
+ // carries a `baseOffset` — the offset assigned to the first record sent
100
+ // to that partition. We don't know per-partition record counts from the
101
+ // response, only the starting offset.
102
+ const offsets = []
103
+ for (const entry of result) {
104
+ // sendBatch hands the same multi-topic response to every per-topic
105
+ // ctx; the span only owns its own topic's entries.
106
+ if (entry.topicName !== ctx.topic) continue
107
+ const offsetAsLong = entry.offset ?? entry.baseOffset
108
+ if (entry.partition === undefined || offsetAsLong === undefined) continue
109
+ // Kafka offsets are 64-bit; coercing to Number loses precision past
110
+ // 2^53. Keep them as strings so the tag matches the exact offset on
111
+ // long-lived/high-throughput topics.
112
+ offsets.push({ partition: entry.partition, start_offset: String(offsetAsLong) })
113
+ }
114
+ if (offsets.length > 0) {
115
+ offsets.sort((a, b) => a.partition - b.partition)
116
+ span.setTag('kafka.messages.offsets', JSON.stringify(offsets))
117
+ }
118
+ // Single-message send: the one entry's partition/offset describes the
119
+ // exact record. Also expose them as flat tags for easy filtering.
120
+ if (offsets.length === 1 && ctx.messages?.length === 1) {
121
+ span.setTag('kafka.partition', offsets[0].partition)
122
+ // Set as a string meta tag (not a metric) to preserve full 64-bit precision.
123
+ span.setTag('kafka.message.offset', offsets[0].start_offset)
124
+ }
125
+ }
126
+ super.finish(ctx)
127
+ }
128
+
94
129
  bindStart (ctx) {
95
130
  const { topic, messages, bootstrapServers, clusterId, disableHeaderInjection } = ctx
96
131
  const span = this.startSpan({
@@ -20,7 +20,7 @@ class PregelStreamPlugin extends TracingPlugin {
20
20
  }
21
21
  class NextStreamPlugin extends TracingPlugin {
22
22
  static id = 'langgraph_stream_next'
23
- static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
23
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream:next'
24
24
 
25
25
  bindStart (ctx) {
26
26
  return ctx.currentStore